/*!
    \file generator_ruby.cpp
    \brief Fast binary encoding Ruby generator implementation
    \author Ivan Shynkarenka
    \date 15.10.2018
    \copyright MIT License
*/

#include "generator_ruby.h"

namespace FBE {

void GeneratorRuby::Generate(const std::shared_ptr<Package>& package)
{
    GeneratePackage(package);
}

void GeneratorRuby::GenerateHeader(const std::string& source)
{
    std::string code = R"CODE(#------------------------------------------------------------------------------
# Automatically generated by the Fast Binary Encoding compiler, do not modify!
# https://github.com/chronoxor/FastBinaryEncoding
# Source: _INPUT_
# FBE version: _VERSION_
#------------------------------------------------------------------------------

# rubocop:disable Lint/MissingCopEnableDirective
# rubocop:disable Lint/UnneededCopDisableDirective
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/ClassLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/LineLength
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/PerceivedComplexity
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFooter()
{
    std::string code = R"CODE(
# rubocop:enable all
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateImports()
{
    std::string code = R"CODE(
require 'base64'
require 'bigdecimal'
require 'json'
require 'set'
require 'uuidtools'
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBE(const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / "fbe.rb";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");
    GenerateImports();

    // Generate module begin
    WriteLine();
    WriteLineIndent("module FBE");
    Indent(1);

    // Generate common models
    GenerateFBEEnum();
    GenerateFBEFlags();
    GenerateFBEInteger();
    GenerateFBEWriteBuffer();
    GenerateFBEReadBuffer();
    GenerateFBEModel();
    GenerateFBEFieldModelBase();
    GenerateFBEFieldModel();
    GenerateFBEFieldModel("Bool", "bool", "1", "false");
    GenerateFBEFieldModel("Byte", "byte", "1", "0");
    GenerateFBEFieldModel("Char", "char", "1", "\"\\0\"");
    GenerateFBEFieldModel("WChar", "wchar", "4", "\"\\0\"");
    GenerateFBEFieldModel("Int8", "int8", "1", "0");
    GenerateFBEFieldModel("UInt8", "uint8", "1", "0");
    GenerateFBEFieldModel("Int16", "int16", "2", "0");
    GenerateFBEFieldModel("UInt16", "uint16", "2", "0");
    GenerateFBEFieldModel("Int32", "int32", "4", "0");
    GenerateFBEFieldModel("UInt32", "uint32", "4", "0");
    GenerateFBEFieldModel("Int64", "int64", "8", "0");
    GenerateFBEFieldModel("UInt64", "uint64", "8", "0");
    GenerateFBEFieldModel("Float", "float", "4", "0.0");
    GenerateFBEFieldModel("Double", "double", "8", "0.0");
    GenerateFBEFieldModel("Timestamp", "timestamp", "8", "0");
    GenerateFBEFieldModel("UUID", "uuid", "16", "UUIDTools::UUID.parse_int(0)");
    GenerateFBEFieldModelDecimal();
    GenerateFBEFieldModelBytes();
    GenerateFBEFieldModelString();
    GenerateFBEFieldModelOptional();
    GenerateFBEFieldModelArray();
    GenerateFBEFieldModelVector();
    GenerateFBEFieldModelSet();
    GenerateFBEFieldModelMap();
    if (Final())
    {
        GenerateFBEFinalModel();
        GenerateFBEFinalModel("Bool", "bool", "1", "false");
        GenerateFBEFinalModel("Byte", "byte", "1", "0");
        GenerateFBEFinalModel("Char", "char", "1", "\"\\0\"");
        GenerateFBEFinalModel("WChar", "wchar", "4", "\"\\0\"");
        GenerateFBEFinalModel("Int8", "int8", "1", "0");
        GenerateFBEFinalModel("UInt8", "uint8", "1", "0");
        GenerateFBEFinalModel("Int16", "int16", "2", "0");
        GenerateFBEFinalModel("UInt16", "uint16", "2", "0");
        GenerateFBEFinalModel("Int32", "int32", "4", "0");
        GenerateFBEFinalModel("UInt32", "uint32", "4", "0");
        GenerateFBEFinalModel("Int64", "int64", "8", "0");
        GenerateFBEFinalModel("UInt64", "uint64", "8", "0");
        GenerateFBEFinalModel("Float", "float", "4", "0.0");
        GenerateFBEFinalModel("Double", "double", "8", "0.0");
        GenerateFBEFinalModel("Timestamp", "timestamp", "8", "0");
        GenerateFBEFinalModel("UUID", "uuid", "16", "UUIDTools::UUID.parse_int(0)");
        GenerateFBEFinalModelDecimal();
        GenerateFBEFinalModelBytes();
        GenerateFBEFinalModelString();
        GenerateFBEFinalModelOptional();
        GenerateFBEFinalModelArray();
        GenerateFBEFinalModelVector();
        GenerateFBEFinalModelSet();
        GenerateFBEFinalModelMap();
    }
    if (Proto())
    {
        GenerateFBESender();
        GenerateFBEReceiver();
    }

    // Generate module end
    Indent(-1);
    WriteLine();
    WriteLineIndent("end");

    // Generate common footer
    GenerateFooter();

    // Close the common file
    WriteEnd();
    Store(common);
}

void GeneratorRuby::GenerateFBEEnum()
{
    std::string code = R"CODE(
  # Fast Binary Encoding enum utility module
  module Enum

    attr_reader :key, :value

    def initialize(key, value)
      @key = key
      @value = value
    end

    def self.included(base)
      base.extend Enumerable
      base.extend ClassMethods
    end

    module ClassMethods
      # Define an enumerated value.
      #
      # === Parameters
      # [key] Enumerator key.
      # [value] Enumerator value.
      def define(key, value)
        @_enum_hash ||= {}
        @_enums_by_value ||= {}

        validate_key!(key)
        store_new_instance(key, value)
        define_singleton_method(key) { value }
      end

      def store_new_instance(key, value)
        new_instance = new(key, value)
        @_enum_hash[key] = new_instance
        @_enums_by_value[value] = new_instance
      end

      def const_missing(key)
        raise "Constant is missing for key '#{key}' in enum '#{name}'"
      end

      # Iterate over all enumerated values.
      # Required for Enumerable mixin
      def each(&block)
        @_enum_hash.each(&block)
      end

      # Attempt to parse an enum key and return the
      # corresponding value.
      #
      # === Parameters
      # [k] The key string to parse.
      #
      # Returns the corresponding value or nil.
      def parse(k)
        k = k.to_s.upcase
        each do |key, enum|
          return enum.value if key.to_s.upcase == k
        end
        nil
      end

      # Whether the specified key exists in this enum.
      #
      # === Parameters
      # [k] The string key to check.
      #
      # Returns true if the key exists, false otherwise.
      def key?(k)
        @_enum_hash.key?(k)
      end

      # Gets the string value for the specified key.
      #
      # === Parameters
      # [k] The key symbol to get the value for.
      #
      # Returns the corresponding enum instance or nil.
      def value(k)
        enum = @_enum_hash[k]
        enum.value if enum
      end

      # Whether the specified value exists in this enum.
      #
      # === Parameters
      # [k] The string value to check.
      #
      # Returns true if the value exists, false otherwise.
      def value?(v)
        @_enums_by_value.key?(v)
      end

      # Gets the key symbol for the specified value.
      #
      # === Parameters
      # [v] The string value to parse.
      #
      # Returns the corresponding key symbol or nil.
      def key(v)
        enum = @_enums_by_value[v]
        enum.key if enum
      end

      # Returns all enum keys.
      def keys
        @_enum_hash.values.map(&:key)
      end

      # Returns all enum values.
      def values
        @_enum_hash.values.map(&:value)
      end

      def to_h
        Hash[@_enum_hash.map do |key, enum|
          [key, enum.value]
        end]
      end

      private

      def upper?(s)
        !/[[:upper:]]/.match(s).nil?
      end

      def validate_key!(key)
        return unless @_enum_hash.key?(key)
        raise "Duplicate key '#{key}' in enum '#{name}'"
      end

      def validate_value!(value)
        return unless @_enums_by_value.key?(value)
        raise "Duplicate value '#{value}' in enum '#{name}'"
      end
    end

  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFlags()
{
    std::string code = R"CODE(
  # Fast Binary Encoding flags utility module
  module Flags

    attr_reader :key, :value

    def initialize(key, value)
      @key = key
      @value = value
    end

    def self.included(base)
      base.extend Enumerable
      base.extend ClassMethods
    end

    module ClassMethods
      # Define an enumerated value.
      #
      # === Parameters
      # [key] Enumerator key.
      # [value] Enumerator value.
      def define(key, value)
        @_enum_hash ||= {}
        @_enums_by_value ||= {}

        validate_key!(key)
        store_new_instance(key, value)
        define_singleton_method(key) { value }

        name = key.to_s

        # Flags check method
        define_method(name + '?') do
          @value & value != 0
        end

        # Flags set/reset method
        define_method(name + '=') do |set|
          if set
            @value |= value
          else
            @value &= ~value
          end
        end
      end

      def store_new_instance(key, value)
        new_instance = new(key, value)
        @_enum_hash[key] = new_instance
        @_enums_by_value[value] = new_instance
      end

      def const_missing(key)
        raise "Constant is missing for key '#{key}' in flags '#{name}'"
      end

      # Iterate over all enumerated values.
      # Required for Enumerable mixin
      def each(&block)
        @_enum_hash.each(&block)
      end

      # Attempt to parse an enum key and return the
      # corresponding value.
      #
      # === Parameters
      # [k] The key string to parse.
      #
      # Returns the corresponding value or nil.
      def parse(k)
        k = k.to_s.upcase
        each do |key, enum|
          return enum.value if key.to_s.upcase == k
        end
        nil
      end

      # Whether the specified key exists in this enum.
      #
      # === Parameters
      # [k] The string key to check.
      #
      # Returns true if the key exists, false otherwise.
      def key?(k)
        @_enum_hash.key?(k)
      end

      # Gets the string value for the specified key.
      #
      # === Parameters
      # [k] The key symbol to get the value for.
      #
      # Returns the corresponding enum instance or nil.
      def value(k)
        enum = @_enum_hash[k]
        enum.value if enum
      end

      # Whether the specified value exists in this enum.
      #
      # === Parameters
      # [k] The string value to check.
      #
      # Returns true if the value exists, false otherwise.
      def value?(v)
        @_enums_by_value.key?(v)
      end

      # Gets the key symbol for the specified value.
      #
      # === Parameters
      # [v] The string value to parse.
      #
      # Returns the corresponding key symbol or nil.
      def key(v)
        enum = @_enums_by_value[v]
        enum.key if enum
      end

      # Returns all enum keys.
      def keys
        @_enum_hash.values.map(&:key)
      end

      # Returns all enum values.
      def values
        @_enum_hash.values.map(&:value)
      end

      def to_h
        Hash[@_enum_hash.map do |key, enum|
          [key, enum.value]
        end]
      end

      private

      def upper?(s)
        !/[[:upper:]]/.match(s).nil?
      end

      def validate_key!(key)
        return unless @_enum_hash.key?(key)
        raise "Duplicate key '#{key}' in flags '#{name}'"
      end

      def validate_value!(value)
        return unless @_enums_by_value.key?(value)
        raise "Duplicate value '#{value}' in flags '#{name}'"
      end
    end

  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEInteger()
{
    std::string code = R"CODE(
  # Fast Binary Encoding integer number constants
  class Integer
    N_BYTES = [42].pack('i').size
    N_BITS = N_BYTES * 16
    MAX = 2 ** (N_BITS - 2) - 1
    MIN = -MAX - 1
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEWriteBuffer()
{
    std::string code = R"CODE(
  # Fast Binary Encoding write buffer based on the dynamic byte array
  class WriteBuffer
    def initialize(capacity = 0)
      @_buffer = capacity > 0 ? [0].pack('C') * capacity : ''
      @_size = 0
      @_offset = 0
    end

    def empty?
      @_buffer.nil? || @_size <= 0
    end

    def buffer
      @_buffer
    end

    def capacity
      @_buffer.length
    end

    def size
      @_size
    end

    def offset
      @_offset
    end

    # Attach an empty memory buffer
    def attach_new
      @_buffer = ''
      @_size = 0
      @_offset = 0
    end

    # Attach an empty memory buffer with a given capacity
    def attach_capacity(capacity)
      @_buffer = [0].pack('C') * capacity
      @_size = 0
      @_offset = 0
    end

    # Attach a given memory buffer
    def attach_buffer(buffer, offset = 0, size = nil)
      raise ArgumentError, "Invalid buffer!" if buffer.nil?

      if buffer.is_a?(String)
        @_buffer = buffer
        size = @_buffer.length if size.nil?
      elsif buffer.is_a?(Array)
        @_buffer = buffer.join
        size = @_buffer.length if size.nil?
      elsif buffer.is_a?(WriteBuffer)
        @_buffer = buffer.buffer
        size = buffer.size if size.nil?
      elsif buffer.is_a?(ReadBuffer)
        @_buffer = buffer.buffer
        size = buffer.size if size.nil?
      else
        raise ArgumentError, "Unknown buffer type!"
      end

      raise ArgumentError, "Invalid size!" if size <= 0
      raise ArgumentError, "Invalid offset!" if offset > size

      @_size = size
      @_offset = offset
    end

    # Allocate memory in the current write buffer and return offset to the allocated memory block
    def allocate(size)
      raise ArgumentError, "Invalid allocation size!" if size < 0

      offset = @_size

      # Calculate a new buffer size
      total = @_size + size

      if total <= @_buffer.length
        @_size = total
        return offset
      end

      @_buffer += [0].pack('C') * [total, 2 * @_buffer.length].max
      @_size = total
      offset
    end

    # Remove some memory of the given size from the current write buffer
    def remove(offset, size)
      raise ArgumentError, "Invalid offset & size!" if (offset + size) > @_buffer.length

      @_buffer.slice!(offset, size)
      @_size -= size
      if @_offset >= (offset + size)
        @_offset -= size
      elsif @_offset >= offset
        @_offset -= @_offset - offset
        @_offset = @_size if @_offset > @_size
      end
    end

    # Reserve memory of the given capacity in the current write buffer
    def reserve(capacity)
      raise ArgumentError, "Invalid reserve capacity!" if capacity < 0

      @_buffer += [0].pack('C') * [capacity, 2 * @_buffer.length].max if capacity > @_buffer.length
    end

    # Resize the current write buffer
    def resize(size)
      reserve(size)
      @_size = size
      @_offset = @_size if @_offset > @_size
    end

    # Reset the current write buffer and its offset
    def reset
      @_size = 0
      @_offset = 0
    end

    # Shift the current write buffer offset
    def shift(offset)
      @_offset += offset
    end

    # Unshift the current write buffer offset
    def unshift(offset)
      @_offset -= offset
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEReadBuffer()
{
    std::string code = R"CODE(
  # Fast Binary Encoding read buffer based on the constant byte buffer
  class ReadBuffer
    def initialize
      @_buffer = nil
      @_size = 0
      @_offset = 0
    end

    def empty?
      @_buffer.nil? || @_size <= 0
    end

    def buffer
      @_buffer
    end

    def capacity
      @_buffer.length
    end

    def size
      @_size
    end

    def offset
      @_offset
    end

    # Attach a given memory buffer
    def attach_buffer(buffer, offset = 0, size = nil)
      raise ArgumentError, "Invalid buffer!" if buffer.nil?

      if buffer.is_a?(String)
        @_buffer = buffer
        size = @_buffer.length if size.nil?
      elsif buffer.is_a?(Array)
        @_buffer = buffer.join
        size = @_buffer.length if size.nil?
      elsif buffer.is_a?(WriteBuffer)
        @_buffer = buffer.buffer
        size = buffer.size if size.nil?
      elsif buffer.is_a?(ReadBuffer)
        @_buffer = buffer.buffer
        size = buffer.size if size.nil?
      else
        raise ArgumentError, "Unknown buffer type!"
      end

      raise ArgumentError, "Invalid size!" if size <= 0
      raise ArgumentError, "Invalid offset!" if offset > size

      @_size = size
      @_offset = offset
    end

    # Reset the current read buffer and its offset
    def reset
      @_buffer = nil
      @_size = 0
      @_offset = 0
    end

    # Shift the current read buffer offset
    def shift(offset)
      @_offset += offset
    end

    # Unshift the current read buffer offset
    def unshift(offset)
      @_offset -= offset
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEModel()
{
    std::string code = R"CODE(
  # Fast Binary Encoding base model
  class Model
    def initialize(buffer = WriteBuffer.new)
      @_buffer = buffer
    end

    def buffer
      @_buffer
    end

    # Attach an empty memory buffer
    def attach_new
      @_buffer.attach_new
    end

    # Attach an empty memory buffer with a given capacity
    def attach_capacity(capacity)
      @_buffer.attach_capacity(capacity)
    end

    # Attach a given memory buffer
    def attach_buffer(buffer, offset = 0, size = nil)
      @_buffer.attach_buffer(buffer, offset, size)
    end

    # Allocate memory in the current write buffer and return offset to the allocated memory block
    def allocate(size)
      @_buffer.allocate(size)
    end

    # Remove some memory of the given size from the current write buffer
    def remove(offset, size)
      @_buffer.remove(offset, size)
    end

    # Reserve memory of the given capacity in the current write buffer
    def reserve(capacity)
      @_buffer.reserve(capacity)
    end

    # Resize the current write buffer
    def resize(size)
      @_buffer.resize(size)
    end

    # Reset the current write buffer and its offset
    def reset
      @_buffer.reset
    end

    # Shift the current write buffer offset
    def shift(offset)
      @_buffer.shift(offset)
    end

    # Unshift the current write buffer offset
    def unshift(offset)
      @_buffer.unshift(offset)
    end

    protected

    # Buffer I/O methods

    def read_uint32(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 4).unpack('L<')[0]
    end

    def write_uint32(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 4] = [value].pack('L<')
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelBase()
{
    std::string code = R"CODE(
  # Fast Binary Encoding base field model
  # noinspection RubyTooManyMethodsInspection
  class FieldModelBase
    def initialize(buffer, offset)
      @_buffer = buffer
      @_offset = offset
    end

    def buffer
      @_buffer
    end

    # Get the field offset
    def fbe_offset
      @_offset
    end

    # Set the field offset
    def fbe_offset=(offset)
      @_offset = offset
    end

    # Get the field size
    def fbe_size
      0
    end

    # Get the field extra size
    def fbe_extra
      0
    end

    # Shift the current field offset
    def fbe_shift(offset)
      @_offset += offset
    end

    # Unshift the current field offset
    def fbe_unshift(offset)
      @_offset -= offset
    end

    protected

    # Buffer I/O methods

    def read_bool(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 1).unpack('C')[0] != 0
    end

    def read_byte(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 1).unpack('C')[0]
    end

    def read_char(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 1).unpack('C')[0].chr
    end

    def read_wchar(offset)
      # noinspection RubyResolve
      @_buffer.buffer.slice(@_buffer.offset + offset, 4).unpack('L<')[0].chr(Encoding::UTF_8)
    end

    def read_int8(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 1).unpack('c')[0]
    end

    def read_uint8(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 1).unpack('C')[0]
    end

    def read_int16(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 2).unpack('s<')[0]
    end

    def read_uint16(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 2).unpack('S<')[0]
    end

    def read_uint16_be(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 2).unpack('S>')[0]
    end

    def read_int32(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 4).unpack('l<')[0]
    end

    def read_uint32(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 4).unpack('L<')[0]
    end

    def read_uint32_be(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 4).unpack('L>')[0]
    end

    def read_int64(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 8).unpack('q<')[0]
    end

    def read_uint64(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 8).unpack('Q<')[0]
    end

    def read_float(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 4).unpack('e')[0]
    end

    def read_double(offset)
      @_buffer.buffer.slice(@_buffer.offset + offset, 8).unpack('E')[0]
    end

    def read_bytes(offset, size)
      @_buffer.buffer.slice(@_buffer.offset + offset, size)
    end

    def read_timestamp(offset)
      nanoseconds = read_uint64(offset)
      Time.at(nanoseconds / 1000000000, nanoseconds % 1000000000, :nsec).utc
    end

    def read_uuid(offset)
      UUIDTools::UUID.parse_raw(@_buffer.buffer.slice(@_buffer.offset + offset, 16))
    end

    def write_bool(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 1] = [value ? 1 : 0].pack('C')
    end

    def write_byte(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 1] = [value].pack('C')
    end

    def write_char(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 1] = [value.ord].pack('C')
    end

    def write_wchar(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 4] = [value.ord].pack('L<')
    end

    def write_int8(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 1] = [value].pack('c')
    end

    def write_uint8(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 1] = [value].pack('C')
    end

    def write_int16(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 2] = [value].pack('s<')
    end

    def write_uint16(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 2] = [value].pack('S<')
    end

    def write_int32(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 4] = [value].pack('l<')
    end

    def write_uint32(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 4] = [value].pack('L<')
    end

    def write_int64(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 8] = [value].pack('q<')
    end

    def write_uint64(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 8] = [value].pack('Q<')
    end

    def write_float(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 4] = [value].pack('e')
    end

    def write_double(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 8] = [value].pack('E')
    end

    def write_bytes(offset, buffer)
      @_buffer.buffer[@_buffer.offset + offset, buffer.length] = buffer
    end

    def write_count(offset, value, value_count)
      (0...value_count).each { |i| @_buffer.buffer[@_buffer.offset + offset + i] = value.chr }
    end

    def write_timestamp(offset, value)
      nanoseconds = value.to_i * 1000000000 + value.nsec
      write_uint64(offset, nanoseconds)
    end

    def write_uuid(offset, value)
      @_buffer.buffer[@_buffer.offset + offset, 16] = value.raw
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModel()
{
    std::string code = R"CODE(
  # Fast Binary Encoding field model
  class FieldModel < FieldModelBase
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Is the value valid?
    def valid?
      verify
    end

    # Check if the value is valid
    def verify
      true
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModel(const std::string& name, const std::string& type, const std::string& size, const std::string& defaults)
{
    std::string code = R"CODE(
  # Fast Binary Encoding _TYPE_ field model
  class FieldModel_NAME_ < FieldModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the field size
    def fbe_size
      _SIZE_
    end

    # Get the value
    def get(defaults = _DEFAULTS_)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return defaults
      end

      read__TYPE_(fbe_offset)
    end

    # Set the value
    def set(value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      write__TYPE_(fbe_offset, value)
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelDecimal()
{
    std::string code = R"CODE(
  # Fast Binary Encoding decimal field model
  class FieldModelDecimal < FieldModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the field size
    def fbe_size
      16
    end

    # Get the decimal value
    def get(defaults = BigDecimal(0))
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return defaults
      end

      # Read decimal parts
      low = read_uint32(fbe_offset)
      mid = read_uint32(fbe_offset + 4)
      high = read_uint32(fbe_offset + 8)
      flags = read_uint32(fbe_offset + 12)

      # Calculate decimal value
      negative = (flags & 0x80000000) != 0
      scale = (flags & 0x7FFFFFFF) >> 16
      result = BigDecimal(high) * 18446744073709551616
      result += BigDecimal(mid) * 4294967296
      result += BigDecimal(low)
      result /= 10 ** scale
      result = -result if negative

      # Return decimal value
      result
    end

    # Set the decimal value
    def set(value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      # Extract decimal parts
      sign, significant_digits, _, exponent = value.split
      number = significant_digits.to_i(10)
      if exponent > significant_digits.length
        number *= 10 ** (exponent - 1)
        scale = 0
      else
        scale = significant_digits.length - exponent
      end

      # Check for decimal number overflow
      bits = number.bit_length
      if (bits < 0) || (bits > 96)
        # Value too big for .NET Decimal (bit length is limited to [0, 96])
        write_count(fbe_offset, 0, fbe_size)
        return
      end

      # Check for decimal scale overflow
      if (scale < 0) || (scale > 28)
        # Value scale exceeds .NET Decimal limit of [0, 28]
        write_count(fbe_offset, 0, fbe_size)
        return
      end

      # Write unscaled value to bytes 0-11
      bytes = []
      while number > 0
        bytes.insert(-1, number & 0xFF)
        number >>= 8
      end
      bytes = bytes.pack("C*")
      write_bytes(fbe_offset, bytes)
      write_count(fbe_offset + bytes.length, 0, 12 - bytes.length)

      # Write scale at byte 14
      write_byte(fbe_offset + 14, scale)

      # Write signum at byte 15
      write_byte(fbe_offset + 15, (sign == -1) ? 0x80 : 0)
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelBytes()
{
    std::string code = R"CODE(
  # Fast Binary Encoding bytes field model
  class FieldModelBytes < FieldModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the field size
    def fbe_size
      4
    end

    # Get the field extra size
    def fbe_extra
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_bytes_offset = read_uint32(fbe_offset)
      if (fbe_bytes_offset == 0) || ((@_buffer.offset + fbe_bytes_offset + 4) > @_buffer.size)
        return 0
      end

      fbe_bytes_size = read_uint32(fbe_bytes_offset)
      4 + fbe_bytes_size
    end

    # Is the bytes value valid?
    def valid?
      verify
    end

    # Check if the bytes value is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return true
      end

      fbe_bytes_offset = read_uint32(fbe_offset)
      if fbe_bytes_offset == 0
        return true
      end

      if (@_buffer.offset + fbe_bytes_offset + 4) > @_buffer.size
        return false
      end

      fbe_bytes_size = read_uint32(fbe_bytes_offset)
      if (@_buffer.offset + fbe_bytes_offset + 4 + fbe_bytes_size) > @_buffer.size
        return false
      end

      true
    end

    # Get the bytes value
    def get(defaults = '')
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return defaults
      end

      fbe_bytes_offset = read_uint32(fbe_offset)
      if fbe_bytes_offset == 0
        return defaults
      end

      if (@_buffer.offset + fbe_bytes_offset + 4) > @_buffer.size
        return defaults
      end

      fbe_bytes_size = read_uint32(fbe_bytes_offset)
      if (@_buffer.offset + fbe_bytes_offset + 4 + fbe_bytes_size) > @_buffer.size
        return defaults
      end

      read_bytes(fbe_bytes_offset + 4, fbe_bytes_size)
    end

    # Set the bytes value
    def set(value)
      raise ArgumentError, "Invalid bytes value!" if value.nil? || !value.is_a?(String)

      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      fbe_bytes_size = value.length
      fbe_bytes_offset = @_buffer.allocate(4 + fbe_bytes_size) - @_buffer.offset
      if (fbe_bytes_offset <= 0) or ((@_buffer.offset + fbe_bytes_offset + 4 + fbe_bytes_size) > @_buffer.size)
        return
      end

      write_uint32(fbe_offset, fbe_bytes_offset)
      write_uint32(fbe_bytes_offset, fbe_bytes_size)
      write_bytes(fbe_bytes_offset + 4, value)
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelString()
{
    std::string code = R"CODE(
  # Fast Binary Encoding string field model
  class FieldModelString < FieldModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the field size
    def fbe_size
      4
    end

    # Get the field extra size
    def fbe_extra
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_string_offset = read_uint32(fbe_offset)
      if (fbe_string_offset == 0) or ((@_buffer.offset + fbe_string_offset + 4) > @_buffer.size)
        return 0
      end

      fbe_string_size = read_uint32(fbe_string_offset)
      4 + fbe_string_size
    end

    # Is the string value valid?
    def valid?
      verify
    end

    # Check if the string value is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return true
      end

      fbe_string_offset = read_uint32(fbe_offset)
      if fbe_string_offset == 0
        return true
      end

      if (@_buffer.offset + fbe_string_offset + 4) > @_buffer.size
        return false
      end

      fbe_string_size = read_uint32(fbe_string_offset)
      if (@_buffer.offset + fbe_string_offset + 4 + fbe_string_size) > @_buffer.size
        return false
      end

      true
    end

    # Get the string value
    def get(defaults = '')
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return defaults
      end

      fbe_string_offset = read_uint32(fbe_offset)
      if fbe_string_offset == 0
        return defaults
      end

      if (@_buffer.offset + fbe_string_offset + 4) > @_buffer.size
        return defaults
      end

      fbe_string_size = read_uint32(fbe_string_offset)
      if (@_buffer.offset + fbe_string_offset + 4 + fbe_string_size) > @_buffer.size
        return defaults
      end

      data = read_bytes(fbe_string_offset + 4, fbe_string_size)
      data.encode!('utf-8')
    end

    # Set the string value
    def set(value)
      raise ArgumentError, "Invalid string value!" if value.nil? || !value.is_a?(String)

      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      data = value.encode('utf-8')

      fbe_string_size = data.length
      fbe_string_offset = @_buffer.allocate(4 + fbe_string_size) - @_buffer.offset
      if (fbe_string_offset <= 0) || ((@_buffer.offset + fbe_string_offset + 4 + fbe_string_size) > @_buffer.size)
        return
      end

      write_uint32(fbe_offset, fbe_string_offset)
      write_uint32(fbe_string_offset, fbe_string_size)
      write_bytes(fbe_string_offset + 4, data)
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelOptional()
{
    std::string code = R"CODE(
  # Fast Binary Encoding optional field model
  class FieldModelOptional < FieldModel
    def initialize(model, buffer, offset)
      super(buffer, offset)
      @_model = model
      @_model.fbe_offset = 0
    end

    # Get the field size
    def fbe_size
      1 + 4
    end

    # Get the field extra size
    def fbe_extra
      unless has_value?
        return 0
      end

      fbe_optional_offset = read_uint32(fbe_offset + 1)
      if (fbe_optional_offset == 0) || ((@_buffer.offset + fbe_optional_offset + 4) > @_buffer.size)
        return 0
      end

      @_buffer.shift(fbe_optional_offset)
      fbe_result = value.fbe_size + value.fbe_extra
      @_buffer.unshift(fbe_optional_offset)
      fbe_result
    end

    # Checks if the object contains a value
    def empty?
      has_value?
    end

    # Checks if the object contains a value
    def has_value?
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return false
      end

      fbe_has_value = read_uint8(fbe_offset)
      fbe_has_value != 0
    end

    # Get the base field model value
    def value
      @_model
    end

    # Is the optional value valid?
    def valid?
      verify
    end

    # Check if the optional value is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return true
      end

      fbe_has_value = read_uint8(fbe_offset)
      if fbe_has_value == 0
        return true
      end

      fbe_optional_offset = read_uint32(fbe_offset + 1)
      if fbe_optional_offset == 0
        return false
      end

      @_buffer.shift(fbe_optional_offset)
      fbe_result = value.verify
      @_buffer.unshift(fbe_optional_offset)
      fbe_result
    end

    # Get the optional value (being phase)
    def get_begin
      unless has_value?
        return 0
      end

      fbe_optional_offset = read_uint32(fbe_offset + 1)
      if fbe_optional_offset <= 0
        return 0
      end

      @_buffer.shift(fbe_optional_offset)
      fbe_optional_offset
    end

    # Get the optional value (end phase)
    def get_end(fbe_begin)
      @_buffer.unshift(fbe_begin)
    end

    # Get the optional value
    def get(defaults = nil)
      fbe_begin = get_begin
      if fbe_begin == 0
        return defaults
      end
      optional = value.get
      get_end(fbe_begin)
      optional
    end

    # Set the optional value (begin phase)
    def set_begin(has_value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_has_value = has_value ? 1 : 0
      write_uint8(fbe_offset, fbe_has_value)
      if fbe_has_value == 0
        return 0
      end

      fbe_optional_size = value.fbe_size
      fbe_optional_offset = @_buffer.allocate(fbe_optional_size) - @_buffer.offset
      if (fbe_optional_offset <= 0) || ((@_buffer.offset + fbe_optional_offset + fbe_optional_size) > @_buffer.size)
        return 0
      end

      write_uint32(fbe_offset + 1, fbe_optional_offset)

      @_buffer.shift(fbe_optional_offset)
      fbe_optional_offset
    end

    # Set the optional value (end phase)
    def set_end(fbe_begin)
      @_buffer.unshift(fbe_begin)
    end

    # Set the optional value
    def set(optional)
      fbe_begin = set_begin(!optional.nil?)
      if fbe_begin == 0
        return
      end
      value.set(optional)
      set_end(fbe_begin)
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelArray()
{
    std::string code = R"CODE(
  # Fast Binary Encoding array field model
  class FieldModelArray < FieldModel
    def initialize(model, buffer, offset, size)
        super(buffer, offset)
        @_model = model
        @_size = size
    end

    # Get the field size
    def fbe_size
      @_size * @_model.fbe_size
    end

    # Get the field extra size
    def fbe_extra
      0
    end

    # Get the array offset
    def offset
      0
    end

    # Get the array size
    def size
      @_size
    end

    # Array index operator
    def [](index)
      raise RuntimeError, "Model is broken!" if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
      raise IndexError, "Index is out of bounds!" if index >= @_size

      @_model.fbe_offset = fbe_offset
      @_model.fbe_shift(index * @_model.fbe_size)
      @_model
    end

    # Is the array valid?
    def valid?
      verify
    end

    # Check if the array is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return false
      end

      @_model.fbe_offset = fbe_offset
      @_size.times do
        unless @_model.verify
          return false
        end
        @_model.fbe_shift(@_model.fbe_size)
      end

      true
    end

    # Get the array
    def get(values = Array.new)
      values.clear

      fbe_model = self[0]
      @_size.times do
        value = fbe_model.get
        values.append(value)
        fbe_model.fbe_shift(fbe_model.fbe_size)
      end

      values
    end

    # Set the array
    def set(values)
      raise ArgumentError, "Invalid values parameter!" if values.nil? || !values.is_a?(Array)

      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      fbe_model = self[0]
      (0...[values.length, @_size].min).each do |i|
        fbe_model.set(values[i])
        fbe_model.fbe_shift(fbe_model.fbe_size)
      end
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelVector()
{
    std::string code = R"CODE(
  # Fast Binary Encoding vector field model
  class FieldModelVector < FieldModel
    def initialize(model, buffer, offset)
      super(buffer, offset)
      @_model = model
    end

    # Get the field size
    def fbe_size
      4
    end

    # Get the field extra size
    def fbe_extra
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_vector_offset = read_uint32(fbe_offset)
      if (fbe_vector_offset == 0) || ((@_buffer.offset + fbe_vector_offset + 4) > @_buffer.size)
        return 0
      end

      fbe_vector_size = read_uint32(fbe_vector_offset)

      fbe_result = 4
      @_model.fbe_offset = fbe_vector_offset + 4
      fbe_vector_size.times do
        fbe_result += @_model.fbe_size + @_model.fbe_extra
        @_model.fbe_shift(@_model.fbe_size)
      end
      fbe_result
    end

    # Get the vector offset
    def offset
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      read_uint32(fbe_offset)
    end

    # Get the vector size
    def size
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_vector_offset = read_uint32(fbe_offset)
      if (fbe_vector_offset == 0) || ((@_buffer.offset + fbe_vector_offset + 4) > @_buffer.size)
        return 0
      end

      read_uint32(fbe_vector_offset)
    end

    # Vector index operator
    def [](index)
      raise RuntimeError, "Model is broken!" if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
      fbe_vector_offset = read_uint32(fbe_offset)
      raise RuntimeError, "Model is broken!" if (fbe_vector_offset <= 0) || ((@_buffer.offset + fbe_vector_offset + 4) > @_buffer.size)
      fbe_vector_size = read_uint32(fbe_vector_offset)
      raise IndexError, "Index is out of bounds!" if index >= fbe_vector_size

      @_model.fbe_offset = fbe_vector_offset + 4
      @_model.fbe_shift(index * @_model.fbe_size)
      @_model
    end

    # Resize the vector and get its first model
    def resize(size)
      fbe_vector_size = size * @_model.fbe_size
      fbe_vector_offset = @_buffer.allocate(4 + fbe_vector_size) - @_buffer.offset
      raise RuntimeError, "Model is broken!" if (fbe_vector_offset <= 0) || ((@_buffer.offset + fbe_vector_offset + 4) > @_buffer.size)

      write_uint32(fbe_offset, fbe_vector_offset)
      write_uint32(fbe_vector_offset, size)
      write_count(fbe_vector_offset + 4, 0, fbe_vector_size)

      @_model.fbe_offset = fbe_vector_offset + 4
      @_model
    end

    # Is the vector valid?
    def valid?
      verify
    end

    # Check if the vector is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return true
      end

      fbe_vector_offset = read_uint32(fbe_offset)
      if fbe_vector_offset == 0
        return true
      end

      if (@_buffer.offset + fbe_vector_offset + 4) > @_buffer.size
        return false
      end

      fbe_vector_size = read_uint32(fbe_vector_offset)

      @_model.fbe_offset = fbe_vector_offset + 4
      fbe_vector_size.times do
        unless @_model.verify
          return false
        end
        @_model.fbe_shift(@_model.fbe_size)
      end

      true
    end

    # Get the vector
    def get(values = Array.new)
      values.clear

      fbe_vector_size = size
      if fbe_vector_size == 0
        return values
      end

      fbe_model = self[0]
      fbe_vector_size.times do
        value = fbe_model.get
        values.append(value)
        fbe_model.fbe_shift(fbe_model.fbe_size)
      end

      values
    end

    # Set the vector
    def set(values)
      raise ArgumentError, "Invalid values parameter!" if values.nil? || !values.is_a?(Array)

      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      fbe_model = resize(values.length)
      values.each do |value|
        fbe_model.set(value)
        fbe_model.fbe_shift(fbe_model.fbe_size)
      end
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelSet()
{
    std::string code = R"CODE(
  # Fast Binary Encoding set field model
  class FieldModelSet < FieldModel
    def initialize(model, buffer, offset)
      super(buffer, offset)
      @_model = model
    end

    # Get the field size
    def fbe_size
      4
    end

    # Get the field extra size
    def fbe_extra
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_set_offset = read_uint32(fbe_offset)
      if (fbe_set_offset == 0) || ((@_buffer.offset + fbe_set_offset + 4) > @_buffer.size)
        return 0
      end

      fbe_set_size = read_uint32(fbe_set_offset)

      fbe_result = 4
      @_model.fbe_offset = fbe_set_offset + 4
      fbe_set_size.times do
        fbe_result += @_model.fbe_size + @_model.fbe_extra
        @_model.fbe_shift(@_model.fbe_size)
      end
      fbe_result
    end

    # Get the set value offset
    def offset
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      read_uint32(fbe_offset)
    end

    # Get the set value size
    def size
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_set_offset = read_uint32(fbe_offset)
      if (fbe_set_offset == 0) || ((@_buffer.offset + fbe_set_offset + 4) > @_buffer.size)
        return 0
      end

      read_uint32(fbe_set_offset)
    end

    # Set index operator
    def [](index)
      raise RuntimeError, "Model is broken!" if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
      fbe_set_offset = read_uint32(fbe_offset)
      raise RuntimeError, "Model is broken!" if (fbe_set_offset <= 0) || ((@_buffer.offset + fbe_set_offset + 4) > @_buffer.size)
      fbe_set_size = read_uint32(fbe_set_offset)
      raise IndexError, "Index is out of bounds!" if index >= fbe_set_size

      @_model.fbe_offset = fbe_set_offset + 4
      @_model.fbe_shift(index * @_model.fbe_size)
      @_model
    end

    # Resize the set and get its first model
    def resize(size)
      fbe_set_size = size * @_model.fbe_size
      fbe_set_offset = @_buffer.allocate(4 + fbe_set_size) - @_buffer.offset
      raise RuntimeError, "Model is broken!" if (fbe_set_offset <= 0) || ((@_buffer.offset + fbe_set_offset + 4) > @_buffer.size)

      write_uint32(fbe_offset, fbe_set_offset)
      write_uint32(fbe_set_offset, size)
      write_count(fbe_set_offset + 4, 0, fbe_set_size)

      @_model.fbe_offset = fbe_set_offset + 4
      @_model
    end

    # Is the set valid?
    def valid?
      verify
    end

    # Check if the set value is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return true
      end

      fbe_set_offset = read_uint32(fbe_offset)
      if fbe_set_offset == 0
        return true
      end

      if (@_buffer.offset + fbe_set_offset + 4) > @_buffer.size
        return false
      end

      fbe_set_size = read_uint32(fbe_set_offset)

      @_model.fbe_offset = fbe_set_offset + 4
      fbe_set_size.times do
        unless @_model.verify
          return false
        end
        @_model.fbe_shift(@_model.fbe_size)
      end

      true
    end

    # Get the set value
    def get(values = Set.new)
      values.clear

      fbe_set_size = size
      if fbe_set_size == 0
        return values
      end

      fbe_model = self[0]
      fbe_set_size.times do
        value = fbe_model.get
        values.add(value)
        fbe_model.fbe_shift(fbe_model.fbe_size)
      end

      values
    end

    # Set the set value
    def set(values)
      raise ArgumentError, "Invalid values parameter!" if values.nil? || !values.is_a?(Set)

      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      fbe_model = resize(values.length)
      values.each do |value|
        fbe_model.set(value)
        fbe_model.fbe_shift(fbe_model.fbe_size)
      end
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFieldModelMap()
{
    std::string code = R"CODE(
  # Fast Binary Encoding map field model
  class FieldModelMap < FieldModel
    def initialize(model_key, model_value, buffer, offset)
      super(buffer, offset)
      @_model_key = model_key
      @_model_value = model_value
    end

    # Get the field size
    def fbe_size
      4
    end

    # Get the field extra size
    def fbe_extra
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_map_offset = read_uint32(fbe_offset)
      if (fbe_map_offset == 0) || ((@_buffer.offset + fbe_map_offset + 4) > @_buffer.size)
        return 0
      end

      fbe_map_size = read_uint32(fbe_map_offset)

      fbe_result = 4
      @_model_key.fbe_offset = fbe_map_offset + 4
      @_model_value.fbe_offset = fbe_map_offset + 4 + @_model_key.fbe_size
      fbe_map_size.times do
        fbe_result += @_model_key.fbe_size + @_model_key.fbe_extra
        @_model_key.fbe_shift(@_model_key.fbe_size + @_model_value.fbe_size)
        fbe_result += @_model_value.fbe_size + @_model_value.fbe_extra
        @_model_value.fbe_shift(@_model_key.fbe_size + @_model_value.fbe_size)
      end
      fbe_result
    end

    # Get the map offset
    def offset
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      read_uint32(fbe_offset)
    end

    # Get the map size
    def size
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      fbe_map_offset = read_uint32(fbe_offset)
      if (fbe_map_offset == 0) || ((@_buffer.offset + fbe_map_offset + 4) > @_buffer.size)
        return 0
      end

      read_uint32(fbe_map_offset)
    end

    # Map index operator
    def [](index)
      raise RuntimeError, "Model is broken!" if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
      fbe_map_offset = read_uint32(fbe_offset)
      raise RuntimeError, "Model is broken!" if (fbe_map_offset <= 0) || ((@_buffer.offset + fbe_map_offset + 4) > @_buffer.size)
      fbe_map_size = read_uint32(fbe_map_offset)
      raise IndexError, "Index is out of bounds!" if index >= fbe_map_size

      @_model_key.fbe_offset = fbe_map_offset + 4
      @_model_value.fbe_offset = fbe_map_offset + 4 + @_model_key.fbe_size
      @_model_key.fbe_shift(index * (@_model_key.fbe_size + @_model_value.fbe_size))
      @_model_value.fbe_shift(index * (@_model_key.fbe_size + @_model_value.fbe_size))
      [@_model_key, @_model_value]
    end

    # Resize the map and get its first model
    def resize(size)
      fbe_map_size = size * (@_model_key.fbe_size + @_model_value.fbe_size)
      fbe_map_offset = @_buffer.allocate(4 + fbe_map_size) - @_buffer.offset
      raise RuntimeError, "Model is broken!" if (fbe_map_offset <= 0) || ((@_buffer.offset + fbe_map_offset + 4) > @_buffer.size)

      write_uint32(fbe_offset, fbe_map_offset)
      write_uint32(fbe_map_offset, size)
      write_count(fbe_map_offset + 4, 0, fbe_map_size)

      @_model_key.fbe_offset = fbe_map_offset + 4
      @_model_value.fbe_offset = fbe_map_offset + 4 + @_model_key.fbe_size
      [@_model_key, @_model_value]
    end

    # Is the map valid?
    def valid?
      verify
    end

    # Check if the map is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return true
      end

      fbe_map_offset = read_uint32(fbe_offset)
      if fbe_map_offset == 0
        return true
      end

      if (@_buffer.offset + fbe_map_offset + 4) > @_buffer.size
        return false
      end

      fbe_map_size = read_uint32(fbe_map_offset)

      @_model_key.fbe_offset = fbe_map_offset + 4
      @_model_value.fbe_offset = fbe_map_offset + 4 + @_model_key.fbe_size
      fbe_map_size.times do
        unless @_model_key.verify
          return false
        end
        @_model_key.fbe_shift(@_model_key.fbe_size + @_model_value.fbe_size)
        unless @_model_value.verify
          return false
        end
        @_model_value.fbe_shift(@_model_key.fbe_size + @_model_value.fbe_size)
      end

      true
    end

    # Get the map
    def get(values = Hash.new)
      values.clear

      fbe_map_size = size
      if fbe_map_size == 0
        return values
      end

      fbe_model_key, fbe_model_value = self[0]
      fbe_map_size.times do
        key = fbe_model_key.get
        value = fbe_model_value.get
        values[key] = value
        fbe_model_key.fbe_shift(fbe_model_key.fbe_size + fbe_model_value.fbe_size)
        fbe_model_value.fbe_shift(fbe_model_key.fbe_size + fbe_model_value.fbe_size)
      end

      values
    end

    # Set the map
    def set(values)
      raise ArgumentError, "Invalid values parameter!" if values.nil? || !values.is_a?(Hash)

      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      fbe_model_key, fbe_model_value = resize(values.length)
      values.each do |key, value|
        fbe_model_key.set(key)
        fbe_model_key.fbe_shift(fbe_model_key.fbe_size + fbe_model_value.fbe_size)
        fbe_model_value.set(value)
        fbe_model_value.fbe_shift(fbe_model_key.fbe_size + fbe_model_value.fbe_size)
      end
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModel()
{
    std::string code = R"CODE(
  # Fast Binary Encoding final model
  class FinalModel < FieldModelBase
    def initialize(buffer, offset)
        super(buffer, offset)
    end

    # Is the value valid?
    def valid?
      verify
    end

    # Check if the value is valid
    def verify
      raise NotImplementedError, "verify() method not implemented!"
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModel(const std::string& name, const std::string& type, const std::string& size, const std::string& defaults)
{
    std::string code = R"CODE(
  # Fast Binary Encoding _TYPE_ final model
  class FinalModel_NAME_ < FinalModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the allocation size
    # noinspection RubyUnusedLocalVariable
    def fbe_allocation_size(value)
      fbe_size
    end

    # Get the final size
    def fbe_size
      _SIZE_
    end

    # Is the value valid?
    def valid?
      verify
    end

    # Check if the value is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return Integer::MAX
      end

      fbe_size
    end

    # Get the value
    def get
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return [_DEFAULTS_, 0]
      end

      [read__TYPE_(fbe_offset), fbe_size]
    end

    # Set the value
    def set(value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      write__TYPE_(fbe_offset, value)
      fbe_size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModelDecimal()
{
    std::string code = R"CODE(
  # Fast Binary Encoding decimal final model
  class FinalModelDecimal < FieldModel
    def initialize(buffer, offset)
        super(buffer, offset)
    end

    # Get the allocation size
    # noinspection RubyUnusedLocalVariable
    def fbe_allocation_size(value)
      fbe_size
    end

    # Get the final size
    def fbe_size
      16
    end

    # Is the decimal value valid?
    def valid?
      verify
    end

    # Check if the decimal value is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return Integer::MAX
      end

      fbe_size
    end

    # Get the decimal value
    def get
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return [BigDecimal(0), 0]
      end

      # Read decimal parts
      low = read_uint32(fbe_offset)
      mid = read_uint32(fbe_offset + 4)
      high = read_uint32(fbe_offset + 8)
      flags = read_uint32(fbe_offset + 12)

      # Calculate decimal value
      negative = (flags & 0x80000000) != 0
      scale = (flags & 0x7FFFFFFF) >> 16
      result = BigDecimal(high) * 18446744073709551616
      result += BigDecimal(mid) * 4294967296
      result += BigDecimal(low)
      result /= 10 ** scale
      result = -result if negative

      # Return decimal value
      [result, fbe_size]
    end

    # Set the decimal value
    def set(value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      # Extract decimal parts
      sign, significant_digits, _, exponent = value.split
      number = significant_digits.to_i(10)
      if exponent > significant_digits.length
        number *= 10 ** (exponent - 1)
        scale = 0
      else
        scale = significant_digits.length - exponent
      end

      # Check for decimal number overflow
      bits = number.bit_length
      if (bits < 0) || (bits > 96)
        # Value too big for .NET Decimal (bit length is limited to [0, 96])
        write_count(fbe_offset, 0, fbe_size)
        return
      end

      # Check for decimal scale overflow
      if (scale < 0) || (scale > 28)
        # Value scale exceeds .NET Decimal limit of [0, 28]
        write_count(fbe_offset, 0, fbe_size)
        return
      end

      # Write unscaled value to bytes 0-11
      bytes = []
      while number > 0
        bytes.insert(-1, number & 0xFF)
        number >>= 8
      end
      bytes = bytes.pack("C*")
      write_bytes(fbe_offset, bytes)
      write_count(fbe_offset + bytes.length, 0, 12 - bytes.length)

      # Write scale at byte 14
      write_byte(fbe_offset + 14, scale)

      # Write signum at byte 15
      write_byte(fbe_offset + 15, (sign == -1) ? 0x80 : 0)

      fbe_size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModelBytes()
{
    std::string code = R"CODE(
  # Fast Binary Encoding bytes final model
  class FinalModelBytes < FinalModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the allocation size
    def fbe_allocation_size(value)
      4 + value.length
    end

    # Is the bytes value valid?
    def valid?
      verify
    end

    # Check if the bytes value is valid
    def verify
      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return Integer::MAX
      end

      fbe_bytes_size = read_uint32(fbe_offset)
      if (@_buffer.offset + fbe_offset + 4 + fbe_bytes_size) > @_buffer.size
        return Integer::MAX
      end

      4 + fbe_bytes_size
    end

    # Get the bytes value
    def get
      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return ['', 0]
      end

      fbe_bytes_size = read_uint32(fbe_offset)
      if (@_buffer.offset + fbe_offset + 4 + fbe_bytes_size) > @_buffer.size
        return ['', 4]
      end

      [read_bytes(fbe_offset + 4, fbe_bytes_size), (4 + fbe_bytes_size)]
    end

    # Set the bytes value
    def set(value)
      raise ArgumentError, "Invalid bytes value!" if value.nil? || !value.is_a?(String)

      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return 0
      end

      fbe_bytes_size = value.length
      if (@_buffer.offset + fbe_offset + 4 + fbe_bytes_size) > @_buffer.size
        return 4
      end

      write_uint32(fbe_offset, fbe_bytes_size)
      write_bytes(fbe_offset + 4, value)
      4 + fbe_bytes_size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModelString()
{
    std::string code = R"CODE(
  # Fast Binary Encoding string final model
  class FinalModelString < FinalModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the allocation size
    def fbe_allocation_size(value)
      4 + 3 * (value.length + 1)
    end

    # Is the string value valid?
    def valid?
      verify
    end

    # Check if the string value is valid
    def verify
      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return Integer::MAX
      end

      fbe_string_size = read_uint32(fbe_offset)
      if (@_buffer.offset + fbe_offset + 4 + fbe_string_size) > @_buffer.size
        return Integer::MAX
      end

      4 + fbe_string_size
    end

    # Get the string value
    def get
      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return ['', 0]
      end

      fbe_string_size = read_uint32(fbe_offset)
      if (@_buffer.offset + fbe_offset + 4 + fbe_string_size) > @_buffer.size
        return ['', 4]
      end

      data = read_bytes(fbe_offset + 4, fbe_string_size)
      [data.encode!('utf-8'), (4 + fbe_string_size)]
    end

    # Set the string value
    def set(value)
      raise ArgumentError, "Invalid string value!" if value.nil? || !value.is_a?(String)

      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return 0
      end

      data = value.encode('utf-8')

      fbe_string_size = data.length
      if (@_buffer.offset + fbe_offset + 4 + fbe_string_size) > @_buffer.size
        return 4
      end

      write_uint32(fbe_offset, fbe_string_size)
      write_bytes(fbe_offset + 4, data)
      4 + fbe_string_size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModelOptional()
{
    std::string code = R"CODE(
  # Fast Binary Encoding optional final model
  class FinalModelOptional < FinalModel
    def initialize(model, buffer, offset)
      super(buffer, offset)
      @_model = model
      @_model.fbe_offset = 0
    end

    # Get the allocation size
    def fbe_allocation_size(optional)
      1 + (optional ? value.fbe_allocation_size(optional) : 0)
    end

    # Checks if the object contains a value
    def empty?
      has_value?
    end

    # Checks if the object contains a value
    def has_value?
      if (@_buffer.offset + fbe_offset + 1) > @_buffer.size
        return false
      end

      fbe_has_value = read_uint8(fbe_offset)
      fbe_has_value != 0
    end

    # Get the base final model value
    def value
      @_model
    end

    # Is the optional value valid?
    def valid?
      verify
    end

    # Check if the optional value is valid
    def verify
      if (@_buffer.offset + fbe_offset + 1) > @_buffer.size
        return Integer::MAX
      end

      fbe_has_value = read_uint8(fbe_offset)
      if fbe_has_value == 0
        return 1
      end

      @_buffer.shift(fbe_offset + 1)
      fbe_result = value.verify
      @_buffer.unshift(fbe_offset + 1)
      1 + fbe_result
    end

    # Get the optional value
    def get
      if (@_buffer.offset + fbe_offset + 1) > @_buffer.size
        return [nil, 0]
      end

      unless has_value?
        return [nil, 1]
      end

      @_buffer.shift(fbe_offset + 1)
      optional = value.get
      @_buffer.unshift(fbe_offset + 1)
      [optional[0], (1 + optional[1])]
    end

    # Set the optional value
    def set(optional)
      if (@_buffer.offset + fbe_offset + 1) > @_buffer.size
        return 0
      end

      fbe_has_value = optional ? 1 : 0
      write_uint8(fbe_offset, fbe_has_value)
      if fbe_has_value == 0
        return 1
      end

      @_buffer.shift(fbe_offset + 1)
      size = value.set(optional)
      @_buffer.unshift(fbe_offset + 1)
      1 + size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModelArray()
{
    std::string code = R"CODE(
  # Fast Binary Encoding array final model
  class FinalModelArray < FinalModel
    def initialize(model, buffer, offset, size)
      super(buffer, offset)
      @_model = model
      @_size = size
    end

    # Get the allocation size
    def fbe_allocation_size(values)
      size = 0
      [values.length, @_size].min.times do |i|
        size += @_model.fbe_allocation_size(values[i])
      end
      size
    end

    # Is the array valid?
    def valid?
      verify
    end

    # Check if the array is valid
    def verify
      if (@_buffer.offset + fbe_offset) > @_buffer.size
        return Integer::MAX
      end

      size = 0
      @_model.fbe_offset = fbe_offset
      @_size.times do
        offset = @_model.verify
        if offset == Integer::MAX
          return Integer::MAX
        end
        @_model.fbe_shift(offset)
        size += offset
      end
      size
    end

    # Get the array
    def get(values = Array.new)
      values.clear

      if (@_buffer.offset + fbe_offset) > @_buffer.size
        return [values, 0]
      end

      size = 0
      @_model.fbe_offset = fbe_offset
      @_size.times do
        value = @_model.get
        values.append(value[0])
        @_model.fbe_shift(value[1])
        size += value[1]
      end
      [values, size]
    end

    # Set the array
    def set(values)
      raise ArgumentError, "Invalid values parameter!" if values.nil? || !values.is_a?(Array)

      if (@_buffer.offset + fbe_offset) > @_buffer.size
        return 0
      end

      size = 0
      @_model.fbe_offset = fbe_offset
      [values.length, @_size].min.times do |i|
        offset = @_model.set(values[i])
        @_model.fbe_shift(offset)
        size += offset
      end
      size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModelVector()
{
    std::string code = R"CODE(
  # Fast Binary Encoding vector final model
  class FinalModelVector < FinalModel
    def initialize(model, buffer, offset)
      super(buffer, offset)
      @_model = model
    end

    # Get the allocation size
    def fbe_allocation_size(values)
      size = 4
      values.each do |value|
        size += @_model.fbe_allocation_size(value)
      end
      size
    end

    # Is the vector valid?
    def valid?
      verify
    end

    # Check if the vector is valid
    def verify
      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return Integer::MAX
      end

      fbe_vector_size = read_uint32(fbe_offset)

      size = 4
      @_model.fbe_offset = fbe_offset + 4
      fbe_vector_size.times do
        offset = @_model.verify
        if offset == Integer::MAX
          return Integer::MAX
        end
        @_model.fbe_shift(offset)
        size += offset
      end
      size
    end

    # Get the vector
    def get(values = Array.new)
      values.clear

      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return [values, 0]
      end

      fbe_vector_size = read_uint32(fbe_offset)
      if fbe_vector_size == 0
        return [values, 4]
      end

      size = 4
      @_model.fbe_offset = fbe_offset + 4
      fbe_vector_size.times do
        value = @_model.get
        values.append(value[0])
        @_model.fbe_shift(value[1])
        size += value[1]
      end
      [values, size]
    end

    # Set the vector
    def set(values)
      raise ArgumentError, "Invalid values parameter!" if values.nil? || !values.is_a?(Array)

      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return 0
      end

      write_uint32(fbe_offset, values.length)

      size = 4
      @_model.fbe_offset = fbe_offset + 4
      values.each do |value|
        offset = @_model.set(value)
        @_model.fbe_shift(offset)
        size += offset
      end
      size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModelSet()
{
    std::string code = R"CODE(
  # Fast Binary Encoding set final model
  class FinalModelSet < FinalModel
    def initialize(model, buffer, offset)
      super(buffer, offset)
      @_model = model
    end

    # Get the allocation size
    def fbe_allocation_size(values)
      size = 4
      values.each do |value|
        size += @_model.fbe_allocation_size(value)
      end
      size
    end

    # Is the set valid?
    def valid?
      verify
    end

    # Check if the set value is valid
    def verify
      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return Integer::MAX
      end

      fbe_set_size = read_uint32(fbe_offset)

      size = 4
      @_model.fbe_offset = fbe_offset + 4
      fbe_set_size.times do
        offset = @_model.verify
        if offset == Integer::MAX
          return Integer::MAX
        end
        @_model.fbe_shift(offset)
        size += offset
      end
      size
    end

    # Get the set value
    def get(values = Set.new)
      values.clear

      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return [values, 0]
      end

      fbe_set_size = read_uint32(fbe_offset)
      if fbe_set_size == 0
        return [values, 4]
      end

      size = 4
      @_model.fbe_offset = fbe_offset + 4
      fbe_set_size.times do
        value = @_model.get
        values.add(value[0])
        @_model.fbe_shift(value[1])
        size += value[1]
      end
      [values, size]
    end

    # Set the set value
    def set(values)
      raise ArgumentError, "Invalid values parameter!" if values.nil? || !values.is_a?(Set)

      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return 0
      end

      write_uint32(fbe_offset, values.length)

      size = 4
      @_model.fbe_offset = fbe_offset + 4
      values.each do |value|
        offset = @_model.set(value)
        @_model.fbe_shift(offset)
        size += offset
      end
      size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEFinalModelMap()
{
    std::string code = R"CODE(
  # Fast Binary Encoding map final model
  class FinalModelMap < FinalModel
    def initialize(model_key, model_value, buffer, offset)
      super(buffer, offset)
      @_model_key = model_key
      @_model_value = model_value
    end

    # Get the allocation size
    def fbe_allocation_size(values)
      size = 4
      values.each do |key, value|
        size += @_model_key.fbe_allocation_size(key)
        size += @_model_value.fbe_allocation_size(value)
      end
      size
    end

    # Is the map valid?
    def valid?
      verify
    end

    # Check if the map is valid
    def verify
      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return Integer::MAX
      end

      fbe_map_size = read_uint32(fbe_offset)

      size = 4
      @_model_key.fbe_offset = fbe_offset + 4
      @_model_value.fbe_offset = fbe_offset + 4
      fbe_map_size.times do
        offset_key = @_model_key.verify
        if offset_key == Integer::MAX
          return Integer::MAX
        end
        @_model_key.fbe_shift(offset_key)
        @_model_value.fbe_shift(offset_key)
        size += offset_key
        offset_value = @_model_value.verify
        if offset_value == Integer::MAX
          return Integer::MAX
        end
        @_model_key.fbe_shift(offset_value)
        @_model_value.fbe_shift(offset_value)
        size += offset_value
      end
      size
    end

    # Get the map
    def get(values = Hash.new)
      values.clear

      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return [values, 0]
      end

      fbe_map_size = read_uint32(fbe_offset)
      if fbe_map_size == 0
        return [values, 4]
      end

      size = 4
      @_model_key.fbe_offset = fbe_offset + 4
      @_model_value.fbe_offset = fbe_offset + 4
      fbe_map_size.times do
        key = @_model_key.get
        @_model_key.fbe_shift(key[1])
        @_model_value.fbe_shift(key[1])
        size += key[1]
        value = @_model_value.get
        @_model_key.fbe_shift(value[1])
        @_model_value.fbe_shift(value[1])
        size += value[1]
        values[key[0]] = value[0]
      end
      [values, size]
    end

    # Set the map
    def set(values)
      raise ArgumentError, "Invalid values parameter!" if values.nil? || !values.is_a?(Hash)

      if (@_buffer.offset + fbe_offset + 4) > @_buffer.size
        return 0
      end

      write_uint32(fbe_offset, values.length)

      size = 4
      @_model_key.fbe_offset = fbe_offset + 4
      @_model_value.fbe_offset = fbe_offset + 4
      values.each do |key, value|
        offset_key = @_model_key.set(key)
        @_model_key.fbe_shift(offset_key)
        @_model_value.fbe_shift(offset_key)
        size += offset_key
        offset_value = @_model_value.set(value)
        @_model_key.fbe_shift(offset_value)
        @_model_value.fbe_shift(offset_value)
        size += offset_value
      end
      size
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBESender()
{
    std::string code = R"CODE(
  # Fast Binary Encoding base sender
  class Sender
    def initialize(buffer = WriteBuffer.new, final = false)
      @_buffer = buffer
      @_logging = false
      @_final = final
    end

    # Get the bytes buffer
    def buffer
      @_buffer
    end

    # Get the final protocol flag
    def final?
      @_final
    end

    # Get the logging flag
    def logging?
      @_logging
    end

    # Set the logging flag
    def logging=(logging)
      @_logging = logging
    end

    # Reset the sender buffer
    def reset
        @_buffer.reset
    end

    # Send serialized buffer.
    # Direct call of the method requires knowledge about internals of FBE models serialization.
    # Use it with care!
    def send_serialized(serialized)
      if serialized <= 0
        return 0
      end

      # Shift the send buffer
      @_buffer.shift(serialized)

      # Send the value
      sent = on_send(@_buffer.buffer, 0, @_buffer.size)
      @_buffer.remove(0, sent)
      sent
    end

    protected

    # Send message handler
    # noinspection RubyUnusedLocalVariable
    def on_send(buffer, offset, size)
      raise NotImplementedError, "Abstract method call!"
    end

    # Send log message handler
    # noinspection RubyUnusedLocalVariable
    def on_send_log(message)
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFBEReceiver()
{
    std::string code = R"CODE(
  # Fast Binary Encoding base receiver
  class Receiver
    def initialize(buffer = WriteBuffer.new, final = false)
      @_buffer = buffer
      @_logging = false
      @_final = final
    end

    # Get the bytes buffer
    def buffer
      @_buffer
    end

    # Get the final protocol flag
    def final?
      @_final
    end

    # Get the logging flag
    def logging?
      @_logging
    end

    # Set the logging flag
    def logging=(logging)
      @_logging = logging
    end

    # Reset the receiver buffer
    def reset
        @_buffer.reset
    end

    # Receive data
    def receive(buffer, offset = 0, size = nil)
      raise ArgumentError, "Invalid buffer!" if buffer.nil?

      if buffer.is_a?(ReadBuffer) || buffer.is_a?(WriteBuffer)
        buffer = buffer.buffer
      end

      if size.nil?
        size = buffer.length
      end

      raise ArgumentError, "Invalid offset & size!" if (offset + size) > buffer.length

      if size == 0
        return
      end

      # Storage buffer
      offset0 = @_buffer.offset
      offset1 = @_buffer.size
      size1 = @_buffer.size

      # Receive buffer
      offset2 = 0
      size2 = size

      # While receive buffer is available to handle...
      while offset2 < size2
        message_buffer = nil
        message_offset = 0
        message_size = 0

        # Try to receive message size
        message_size_copied = false
        message_size_found = false
        until message_size_found
          # Look into the storage buffer
          if offset0 < size1
            count = [size1 - offset0, 4].min
            if count == 4
              message_size_copied = true
              message_size_found = true
              message_size = Receiver.read_uint32(@_buffer.buffer, @_buffer.offset + offset0)
              offset0 += 4
              break
            else
              # Fill remaining data from the receive buffer
              if offset2 < size2
                count = [size2 - offset2, 4 - count].min

                # Allocate and refresh the storage buffer
                @_buffer.allocate(count)
                size1 += count

                @_buffer.buffer[offset1, count] = buffer[offset + offset2, count]
                offset1 += count
                offset2 += count
                next
              else
                break
              end
            end
          end

          # Look into the receive buffer
          if offset2 < size2
            count = [size2 - offset2, 4].min
            if count == 4
              message_size_found = true
              message_size = Receiver.read_uint32(buffer, offset + offset2)
              offset2 += 4
              break
            else
              # Allocate and refresh the storage buffer
              @_buffer.allocate(count)
              size1 += count

              @_buffer.buffer[offset1, count] = buffer[offset + offset2, count]
              offset1 += count
              offset2 += count
              next
            end
          else
            break
          end
        end

        unless message_size_found
          return
        end

        # Check the message full size
        min_size = @_final ? (4 + 4) : (4 + 4 + 4 + 4)
        if message_size < min_size
          return
        end

        # Try to receive message body
        message_found = false
        until message_found
          # Look into the storage buffer
          if offset0 < size1
            count = [size1 - offset0, message_size - 4].min
            if count == (message_size - 4)
              message_found = true
              message_buffer = @_buffer.buffer
              message_offset = offset0 - 4
              offset0 += message_size - 4
              break
            else
              # Fill remaining data from the receive buffer
              if offset2 < size2
                # Copy message size into the storage buffer
                unless message_size_copied
                  # Allocate and refresh the storage buffer
                  @_buffer.allocate(4)
                  size1 += 4

                  Receiver.write_uint32(@_buffer.buffer, @_buffer.offset + offset0, message_size)
                  offset0 += 4
                  offset1 += 4

                  message_size_copied = true
                end

                count = [size2 - offset2, message_size - 4 - count].min

                # Allocate and refresh the storage buffer
                @_buffer.allocate(count)
                size1 += count

                @_buffer.buffer[offset1, count] = buffer[offset + offset2, count]
                offset1 += count
                offset2 += count
                next
              else
                break
              end
            end
          end

          # Look into the receive buffer
          if offset2 < size2
            count = [size2 - offset2, message_size - 4].min
            if !message_size_copied && (count == (message_size - 4))
              message_found = true
              message_buffer = buffer
              message_offset = offset + offset2 - 4
              offset2 += message_size - 4
              break
            else
              # Copy message size into the storage buffer
              unless message_size_copied
                # Allocate and refresh the storage buffer
                @_buffer.allocate(4)
                size1 += 4

                Receiver.write_uint32(@_buffer.buffer, @_buffer.offset + offset0, message_size)
                offset0 += 4
                offset1 += 4

                message_size_copied = true
              end

              # Allocate and refresh the storage buffer
              @_buffer.allocate(count)
              size1 += count

              @_buffer.buffer[offset1, count] = buffer[offset + offset2, count]
              offset1 += count
              offset2 += count
              next
            end
          else
            break
          end
        end

        unless message_found
          # Copy message size into the storage buffer
          unless message_size_copied
            # Allocate and refresh the storage buffer
            @_buffer.allocate(4)
            # noinspection RubyUnusedLocalVariable
            size1 += 4

            Receiver.write_uint32(@_buffer.buffer, @_buffer.offset + offset0, message_size)
            # noinspection RubyUnusedLocalVariable
            offset0 += 4
            # noinspection RubyUnusedLocalVariable
            offset1 += 4

            # noinspection RubyUnusedLocalVariable
            message_size_copied = true
          end
          return
        end

        # Read the message parameters
        if @_final
          # noinspection RubyUnusedLocalVariable
          fbe_struct_size = Receiver.read_uint32(message_buffer, message_offset)
          fbe_struct_type = Receiver.read_uint32(message_buffer, message_offset + 4)
        else
          fbe_struct_offset = Receiver.read_uint32(message_buffer, message_offset + 4)
          # noinspection RubyUnusedLocalVariable
          fbe_struct_size = Receiver.read_uint32(message_buffer, message_offset + fbe_struct_offset)
          fbe_struct_type = Receiver.read_uint32(message_buffer, message_offset + fbe_struct_offset + 4)
        end

        # Handle the message
        on_receive(fbe_struct_type, message_buffer, message_offset, message_size)

        # Reset the storage buffer
        @_buffer.reset

        # Refresh the storage buffer
        offset0 = @_buffer.offset
        offset1 = @_buffer.size
        size1 = @_buffer.size
      end
    end

    protected

    # Receive message handler
    # noinspection RubyUnusedLocalVariable
    def on_receive(type, buffer, offset, size)
      raise NotImplementedError, "Abstract method call!"
    end

    # Receive log message handler
    # noinspection RubyUnusedLocalVariable
    def on_receive_log(message)
    end

    private

    # Buffer I/O methods

    def self.read_uint32(buffer, offset)
      buffer.slice(offset, 4).unpack('L<')[0]
    end

    def self.write_uint32(buffer, offset, value)
      buffer[offset, 4] = [value].pack('L<')
    end
  end
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateImports(const std::shared_ptr<Package>& p)
{
    // Generate common import
    GenerateImports();

    // Generate FBE import
    WriteLine();
    WriteLineIndent("require_relative 'fbe'");

    // Generate packages import
    if (p->import)
        for (const auto& import : p->import->imports)
            WriteLineIndent("require_relative '" + *import + "'");
}

void GeneratorRuby::GeneratePackage(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate FBE file
    GenerateFBE(output);

    // Generate the output file
    output /= *p->name + ".rb";
    WriteBegin();

    // Generate package header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    // Generate module begin
    WriteLine();
    WriteLineIndent("module " + ConvertTitle(*p->name));
    Indent(1);

    // Generate namespace
    if (p->body)
    {
        // Generate child enums
        for (const auto& child_e : p->body->enums)
            GenerateEnum(child_e);

        // Generate child flags
        for (const auto& child_f : p->body->flags)
            GenerateFlags(child_f);

        // Generate child structs
        for (const auto& child_s : p->body->structs)
            GenerateStruct(child_s);
    }

    // Generate protocol
    if (Proto())
    {
        // Generate protocol version
        GenerateProtocolVersion(p);

        // Generate sender & receiver
        GenerateSender(p, false);
        GenerateReceiver(p, false);
        GenerateProxy(p, false);
        if (Final())
        {
            GenerateSender(p, true);
            GenerateReceiver(p, true);
        }
    }

    // Generate module end
    Indent(-1);
    WriteLine();
    WriteLineIndent("end");

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorRuby::GenerateEnum(const std::shared_ptr<EnumType>& e)
{
    std::string enum_name = ConvertTitle(*e->name);
    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Generate enum module
    WriteLine();
    WriteLineIndent("module " + enum_name);
    Indent(1);

    // Generate enum class
    WriteLineIndent("class Enum");
    Indent(1);
    WriteLineIndent("include FBE::Enum");

    // Generate enum class body
    if (e->body && !e->body->values.empty())
    {
        WriteLine();
        int index = 0;
        std::string last = ConvertEnumConstant("int32", "0", false);
        for (const auto& value : e->body->values)
        {
            WriteIndent("define :" + *value->name + ", ");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant(enum_type, *value->value->constant, false);
                    Write(last + " + " + std::to_string(index++));
                }
                else if (value->value->reference && !value->value->reference->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant("", *value->value->reference, false);
                    Write(last);
                }
            }
            else
                Write(last + " + " + std::to_string(index++));
            WriteLine();
        }
        WriteLine();
    }

    // Generate enum initialize method
    WriteLineIndent("def initialize(value = 0)");
    Indent(1);
    WriteLineIndent("@value = value.is_a?(Enum) ? value.value : value");
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum compare operators
    WriteLine();
    WriteLineIndent("# Enum compare operators");
    WriteLineIndent("def ==(value) @value == value.value end");
    WriteLineIndent("def !=(value) @value != value.value end");

    // Generate enum hash & equals methods
    WriteLine();
    WriteLineIndent("# Enum equals");
    WriteLineIndent("def eql?(other)");
    Indent(1);
    WriteLineIndent("self == other");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Enum hash code");
    WriteLineIndent("def hash");
    Indent(1);
    WriteLineIndent("@value.hash");
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum to_i method
    WriteLine();
    WriteLineIndent("# Get enum integer value");
    WriteLineIndent("def to_i");
    Indent(1);
    WriteLineIndent("@value");
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum to_s method
    WriteLine();
    WriteLineIndent("# Get enum string value");
    WriteLineIndent("def to_s");
    Indent(1);
    if (e->body && !e->body->values.empty())
    {
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
        {
            WriteLineIndent("if @value == Enum." + *(*it)->name);
            Indent(1);
            WriteLineIndent("return '" + *(*it)->name + "'");
            Indent(-1);
            WriteLineIndent("end");
        }
        WriteLineIndent("'<unknown>'");
    }
    else
        WriteLineIndent("'<empty>'");
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum JSON methods
    WriteLine();
    WriteLineIndent("# Get enum JSON value");
    WriteLineIndent("def __to_json_map__");
    Indent(1);
    WriteLineIndent("@value");
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum class end
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum module class
    WriteLine();
    WriteLineIndent("class << self");
    Indent(1);
    if (e->body)
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
            WriteLineIndent("attr_accessor :" + *(*it)->name);
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum module class attributes
    if (e->body && !e->body->values.empty())
    {
        WriteLine();
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
            WriteLineIndent("self." + *(*it)->name + " = Enum.new(Enum." + *(*it)->name + ")");
    }

    // Generate enum module class constructor
    WriteLine();
    WriteLineIndent("def self.new(value = 0)");
    Indent(1);
    WriteLineIndent("Enum.new(value)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum module JSON converter
    WriteLine();
    WriteLineIndent("# Get enum value from JSON");
    WriteLineIndent("def self.__from_json_map__(json)");
    Indent(1);
    WriteLineIndent("Enum.new(json)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum module end
    Indent(-1);
    WriteLineIndent("end");

    // Generate enum module freeze
    WriteLine();
    WriteLineIndent(enum_name + ".freeze");

    // Generate enum field model
    GenerateEnumFieldModel(e);

    // Generate enum final model
    if (Final())
        GenerateEnumFinalModel(e);
}

void GeneratorRuby::GenerateEnumFieldModel(const std::shared_ptr<EnumType>& e)
{
    std::string code = R"CODE(
  # Fast Binary Encoding _ENUM_NAME_ field model
  class FieldModel_ENUM_NAME_ < FBE::FieldModel
    def initialize(buffer, offset)
        super(buffer, offset)
    end

    # Get the field size
    def fbe_size
      _ENUM_SIZE_
    end

    # Get the value
    def get(defaults = _ENUM_NAME_.new)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return defaults
      end

      _ENUM_NAME_.new(read__ENUM_TYPE_(fbe_offset))
    end

    # Set the value
    def set(value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      write__ENUM_TYPE_(fbe_offset, value.value)
    end
  end
)CODE";

    std::string enum_name = ConvertTitle(*e->name);
    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), enum_name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), ConvertEnumType(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_SIZE_"), ConvertEnumSize(enum_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateEnumFinalModel(const std::shared_ptr<EnumType>& e)
{
    std::string code = R"CODE(
  # Fast Binary Encoding _ENUM_NAME_ final model
  class FinalModel_ENUM_NAME_ < FBE::FinalModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the allocation size
    # noinspection RubyUnusedLocalVariable
    def fbe_allocation_size(value)
      fbe_size
    end

    # Get the final size
    def fbe_size
      _ENUM_SIZE_
    end

    # Is the enum value valid?
    def valid?
      verify
    end

    # Check if the value is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return FBE::Integer::MAX
      end

      fbe_size
    end

    # Get the value
    def get
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return [_ENUM_NAME_.new, 0]
      end

      [_ENUM_NAME_.new(read__ENUM_TYPE_(fbe_offset)), fbe_size]
    end

    # Set the value
    def set(value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      write__ENUM_TYPE_(fbe_offset, value.value)
      fbe_size
    end
  end
)CODE";

    std::string enum_name = ConvertTitle(*e->name);
    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), enum_name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), ConvertEnumType(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_SIZE_"), ConvertEnumSize(enum_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFlags(const std::shared_ptr<FlagsType>& f)
{
    std::string flags_name = ConvertTitle(*f->name);
    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Generate flags module
    WriteLine();
    WriteLineIndent("module " + flags_name);
    Indent(1);

    // Generate flags class
    WriteLineIndent("class Flags");
    Indent(1);
    WriteLineIndent("include FBE::Flags");

    // Generate flags class body
    if (f->body && !f->body->values.empty())
    {
        WriteLine();
        for (const auto& value : f->body->values)
        {
            WriteIndent("define :" + *value->name + ", ");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                    Write(ConvertEnumConstant(flags_type, *value->value->constant, true));
                else if (value->value->reference && !value->value->reference->empty())
                    Write(ConvertEnumConstant("", *value->value->reference, true));
            }
            WriteLine();
        }
        WriteLine();
    }

    // Generate flags initialize method
    WriteLineIndent("def initialize(value = 0)");
    Indent(1);
    WriteLineIndent("@value = value.is_a?(Flags) ? value.value : value");
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags compare operators
    WriteLine();
    WriteLineIndent("# Flags compare operators");
    WriteLineIndent("def ==(flags) @value == flags.value end");
    WriteLineIndent("def !=(flags) @value != flags.value end");

    // Generate flags bit operators
    WriteLine();
    WriteLineIndent("# Flags bit operators");
    WriteLineIndent("def ~");
    Indent(1);
    WriteLineIndent("Flags.new(~@value)");
    Indent(-1);
    WriteLineIndent("end");
    WriteLineIndent("def &(flags) Flags.new(@value & flags.value) end");
    WriteLineIndent("def |(flags) Flags.new(@value | flags.value) end");
    WriteLineIndent("def ^(flags) Flags.new(@value ^ flags.value) end");

    // Generate flags hash & equals methods
    WriteLine();
    WriteLineIndent("# Flags equals");
    WriteLineIndent("def eql?(other)");
    Indent(1);
    WriteLineIndent("self == other");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Flags hash code");
    WriteLineIndent("def hash");
    Indent(1);
    WriteLineIndent("@value.hash");
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags manipulation methods
    WriteLine();
    WriteLineIndent("# Is flags set?");
    WriteLineIndent("def has_flags(flags)");
    Indent(1);
    WriteLineIndent("((@value & flags.value) != 0) && ((@value & flags.value) == flags.value)");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Set flags");
    WriteLineIndent("def set_flags(flags)");
    Indent(1);
    WriteLineIndent("@value |= flags.value");
    WriteLineIndent("self");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Remove flags");
    WriteLineIndent("def remove_flags(flags)");
    Indent(1);
    WriteLineIndent("@value &= ~flags.value");
    WriteLineIndent("self");
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags to_i method
    WriteLine();
    WriteLineIndent("# Get flags integer value");
    WriteLineIndent("def to_i");
    Indent(1);
    WriteLineIndent("@value");
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags to_s method
    WriteLine();
    WriteLineIndent("# Get flags string value");
    WriteLineIndent("def to_s");
    Indent(1);
    WriteLineIndent("result = ''");
    if (f->body && !f->body->values.empty())
    {
        WriteLineIndent("first = true");
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
        {
            WriteLineIndent("if ((@value & Flags." + *(*it)->name + ") != 0) && ((@value & Flags." + *(*it)->name + ") == Flags." + *(*it)->name + ")");
            Indent(1);
            WriteLineIndent("if first");
            Indent(1);
            WriteLineIndent("# noinspection RubyUnusedLocalVariable");
            WriteLineIndent("first = false");
            Indent(-1);
            WriteLineIndent("else");
            Indent(1);
            WriteLineIndent("result << '|'");
            Indent(-1);
            WriteLineIndent("end");
            WriteLineIndent("result << '" + *(*it)->name + "'");
            Indent(-1);
            WriteLineIndent("end");
        }
    }
    WriteLineIndent("result");
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags JSON methods
    WriteLine();
    WriteLineIndent("# Get flags JSON value");
    WriteLineIndent("def __to_json_map__");
    Indent(1);
    WriteLineIndent("@value");
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags class end
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags module class
    WriteLine();
    WriteLineIndent("class << self");
    Indent(1);
    if (f->body)
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
            WriteLineIndent("attr_accessor :" + *(*it)->name);
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags module class attributes
    if (f->body && !f->body->values.empty())
    {
        WriteLine();
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
            WriteLineIndent("self." + *(*it)->name + " = Flags.new(Flags." + *(*it)->name + ")");
    }

    // Generate flags module class constructor
    WriteLine();
    WriteLineIndent("def self.new(value = 0)");
    Indent(1);
    WriteLineIndent("Flags.new(value)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags module JSON converter
    WriteLine();
    WriteLineIndent("# Get flags value from JSON");
    WriteLineIndent("def self.__from_json_map__(json)");
    Indent(1);
    WriteLineIndent("Flags.new(json)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags module end
    Indent(-1);
    WriteLineIndent("end");

    // Generate flags module freeze
    WriteLine();
    WriteLineIndent(flags_name + ".freeze");

    // Generate flags field model
    GenerateFlagsFieldModel(f);

    // Generate flags final model
    if (Final())
        GenerateFlagsFinalModel(f);
}

void GeneratorRuby::GenerateFlagsFieldModel(const std::shared_ptr<FlagsType>& f)
{
    std::string code = R"CODE(
  # Fast Binary Encoding _FLAGS_NAME_ field model
  class FieldModel_FLAGS_NAME_ < FBE::FieldModel
    def initialize(buffer, offset)
        super(buffer, offset)
    end

    # Get the field size
    def fbe_size
      _FLAGS_SIZE_
    end

    # Get the value
    def get(defaults = _FLAGS_NAME_.new)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return defaults
      end

      _FLAGS_NAME_.new(read__FLAGS_TYPE_(fbe_offset))
    end

    # Set the value
    def set(value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return
      end

      write__FLAGS_TYPE_(fbe_offset, value.value)
    end
  end
)CODE";

    std::string flags_name = ConvertTitle(*f->name);
    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), flags_name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), ConvertEnumType(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_SIZE_"), ConvertEnumSize(flags_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateFlagsFinalModel(const std::shared_ptr<FlagsType>& f)
{
    std::string code = R"CODE(
  # Fast Binary Encoding _FLAGS_NAME_ final model
  class FinalModel_FLAGS_NAME_ < FBE::FinalModel
    def initialize(buffer, offset)
      super(buffer, offset)
    end

    # Get the allocation size
    # noinspection RubyUnusedLocalVariable
    def fbe_allocation_size(value)
      fbe_size
    end

    # Get the final size
    def fbe_size
      _FLAGS_SIZE_
    end

    # Is the flags value valid?
    def valid?
      verify
    end

    # Check if the value is valid
    def verify
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return FBE::Integer::MAX
      end

      fbe_size
    end

    # Get the value
    def get
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return [_FLAGS_NAME_.new, 0]
      end

      [_FLAGS_NAME_.new(read__FLAGS_TYPE_(fbe_offset)), fbe_size]
    end

    # Set the value
    def set(value)
      if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size
        return 0
      end

      write__FLAGS_TYPE_(fbe_offset, value.value)
      fbe_size
    end
  end
)CODE";

    std::string flags_name = ConvertTitle(*f->name);
    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), flags_name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), ConvertEnumType(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_SIZE_"), ConvertEnumSize(flags_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorRuby::GenerateStruct(const std::shared_ptr<StructType>& s)
{
    std::string struct_name = ConvertTitle(*s->name);
    std::string base_type = (s->base && !s->base->empty()) ? ConvertTypeName(*s->base, false) : "";

    // Generate struct begin
    WriteLine();
    WriteLineIndent("# noinspection RubyResolve, RubyScope, RubyTooManyInstanceVariablesInspection, RubyTooManyMethodsInspection");
    if (s->base && !s->base->empty())
        WriteLineIndent("class " + struct_name + " < " + base_type);
    else
        WriteLineIndent("class " + struct_name);
    Indent(1);

    // Generate struct include
    WriteLineIndent("include Comparable");

    // Generate struct accessors
    if (s->body && !s->body->fields.empty())
    {
        WriteLine();
        for (const auto& field : s->body->fields)
            WriteLineIndent("attr_accessor :" + *field->name);
        WriteLine();
    }

    // Generate struct constructor
    WriteIndent("def initialize(");
    bool first = true;
    if (s->base && !s->base->empty())
    {
        Write("parent = " + base_type + ".new");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            Write((first ? "" : ", ") + *field->name + " = ");
            if (field->value)
                Write(ConvertConstant(*field->type, *field->value, field->optional));
            else
                Write(ConvertDefault(*field));
            first = false;
        }
    }
    WriteLine(")");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("method(:initialize_copy).super_method.call(parent)");
    if (s->body)
    {
        for (const auto& field : s->body->fields)
            WriteLineIndent("@" + *field->name + " = " + *field->name);
    }
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct copy constructor
    WriteLine();
    WriteLineIndent("def initialize_copy(other)");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("super(other)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("@" + *field->name + " = other." + *field->name);
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct copy() method
    WriteLine();
    WriteLineIndent("# Struct shallow copy");
    WriteLineIndent("def copy(other)");
    Indent(1);
    WriteLineIndent("initialize_copy(other)");
    WriteLineIndent("self");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct clone() method
    WriteLine();
    WriteLineIndent("# Struct deep clone");
    WriteLineIndent("def clone");
    Indent(1);
    WriteLineIndent("data = Marshal.dump(self)");
    WriteLineIndent("clone = Marshal.load(data)");
    WriteLineIndent("clone.freeze if frozen?");
    WriteLineIndent("clone");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct compare operators
    WriteLine();
    WriteLineIndent("# Struct compare operators");
    WriteLineIndent("def <=>(other)");
    Indent(1);
    WriteLineIndent("return nil unless other.is_a?(" + struct_name + ")");
    WriteLine();
    WriteLineIndent("# noinspection RubyUnusedLocalVariable");
    WriteLineIndent("result = 0");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("result = super");
        WriteLineIndent("if result != 0");
        Indent(1);
        WriteLineIndent("return result");
        Indent(-1);
        WriteLineIndent("end");
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("result = @" + *field->name + " <=> other." + *field->name);
                WriteLineIndent("if result != 0");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("end");
            }
        }
    }
    WriteLineIndent("# noinspection RubyUnnecessaryReturnValue");
    WriteLineIndent("result");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct hash & equals methods
    WriteLine();
    WriteLineIndent("# Struct equals");
    WriteLineIndent("def eql?(other)");
    Indent(1);
    WriteLineIndent("self == other");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Struct keys");
    WriteLineIndent("def key");
    Indent(1);
    WriteLineIndent("result = []");
    if (s->base && !s->base->empty())
        WriteLineIndent("result.push(super)");
    if (s->body)
    {
        for (const auto& field : s->body->fields)
            if (field->keys)
                WriteLineIndent("result.push(@" + *field->name + ")");
    }
    WriteLineIndent("# noinspection RubyUnnecessaryReturnValue");
    WriteLineIndent("result");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Struct hash code");
    WriteLineIndent("def hash");
    Indent(1);
    WriteLineIndent("key.hash");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct __str__ method
    WriteLine();
    WriteLineIndent("# Get struct string value");
    WriteLineIndent("def to_s");
    Indent(1);
    WriteLineIndent("result = ''");
    WriteLineIndent("result << '" + *s->name + "('");
    first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("result << super");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("result << '" + std::string(first ? "" : ",") + *field->name + "='");
            if (field->attributes && field->attributes->hidden)
                WriteLineIndent("result << '***'");
            else if (field->array || field->vector)
            {
                WriteLineIndent("if @" + *field->name + ".nil?");
                Indent(1);
                WriteLineIndent("result << '[0][]'");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("first = true");
                WriteLineIndent("result << '[' << @" + *field->name + ".length.to_s << ']['");
                WriteLineIndent("@" + *field->name + ".each do |item|");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", true);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("result << ']'");
                Indent(-1);
                WriteLineIndent("end");
            }
            else if (field->list)
            {
                WriteLineIndent("if @" + *field->name + ".nil?");
                Indent(1);
                WriteLineIndent("result << '[0]<>'");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("first = true");
                WriteLineIndent("result << '[' << @" + *field->name + ".length.to_s << ']<'");
                WriteLineIndent("@" + *field->name + ".each do |item|");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", true);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("result << '>'");
                Indent(-1);
                WriteLineIndent("end");
            }
            else if (field->set)
            {
                WriteLineIndent("if @" + *field->name + ".nil?");
                Indent(1);
                WriteLineIndent("result << '[0]{}'");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("first = true");
                WriteLineIndent("result << '[' << @" + *field->name + ".length.to_s << ']{'");
                WriteLineIndent("@" + *field->name + ".each do |item|");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", true);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("result << '}'");
                Indent(-1);
                WriteLineIndent("end");
            }
            else if (field->map)
            {
                WriteLineIndent("if @" + *field->name + ".nil?");
                Indent(1);
                WriteLineIndent("result << '[0]<{}>'");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("first = true");
                WriteLineIndent("result << '[' << @" + *field->name + ".length.to_s << ']<{'");
                WriteLineIndent("@" + *field->name + ".each do |key, value|");
                Indent(1);
                WriteOutputStreamValue(*field->key, "key", true);
                WriteLineIndent("result << '->'");
                WriteOutputStreamValue(*field->type, "value", false);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("result << '}>'");
                Indent(-1);
                WriteLineIndent("end");
            }
            else if (field->hash)
            {
                WriteLineIndent("if @" + *field->name + ".nil?");
                Indent(1);
                WriteLineIndent("result << '[0][{}]'");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("first = true");
                WriteLineIndent("result << '[' << @" + *field->name + ".length.to_s << '][{'");
                WriteLineIndent("@" + *field->name + ".each do |key, value|");
                Indent(1);
                WriteOutputStreamValue(*field->key, "key", true);
                WriteLineIndent("result << '->'");
                WriteOutputStreamValue(*field->type, "value", false);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("result << '}]'");
                Indent(-1);
                WriteLineIndent("end");
            }
            else
                WriteOutputStreamValue(*field->type, "@" + *field->name, false);
            first = false;
        }
    }
    WriteLineIndent("result << ')'");
    WriteLineIndent("result");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct marshal_dump & marshal_load methods
    WriteLine();
    WriteLineIndent("# Dump the struct");
    WriteLineIndent("def marshal_dump");
    Indent(1);
    WriteLineIndent("# Serialize the struct to the FBE stream");
    WriteLineIndent("writer = " + *s->name + "Model.new(FBE::WriteBuffer.new)");
    WriteLineIndent("writer.serialize(self)");
    WriteLineIndent("writer.buffer");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Load the struct");
    WriteLineIndent("def marshal_load(data)");
    Indent(1);
    WriteLineIndent("# Deserialize the struct from the FBE stream");
    WriteLineIndent("reader = " + *s->name + "Model.new(FBE::ReadBuffer.new)");
    WriteLineIndent("reader.attach_buffer(data)");
    WriteLineIndent("initialize_copy(reader.deserialize[0])");
    Indent(-1);
    WriteLineIndent("end");

    if (JSON())
    {
        // Generate struct to_json method
        WriteLine();
        WriteLineIndent("# Get struct JSON value");
        WriteLineIndent("def to_json");
        Indent(1);
        WriteLineIndent("JSON.generate(__to_json_map__)");
        Indent(-1);
        WriteLineIndent("end");

        // Generate struct __to_json_map__ method
        WriteLine();
        WriteLineIndent("# Get struct JSON map (internal method)");
        WriteLineIndent("def __to_json_map__");
        Indent(1);
        WriteLineIndent("result = {}");
        if (s->base && !s->base->empty())
            WriteLineIndent("result.update(super)");
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLineIndent("key = '" + *field->name + "'");
                if (field->array || field->vector || field->list || field->set)
                    WriteLineIndent("value = " + *field->name + ".map { |item| " + ConvertValueToJson(*field->type, "item", field->optional) + " }");
                else if (field->map || field->hash)
                    WriteLineIndent("value = " + *field->name + ".map { |key, value| [key.to_s, " + ConvertValueToJson(*field->type, "value", field->optional) + "] }.to_h");
                else
                    WriteLineIndent("value = " + ConvertValueToJson(*field->type, *field->name, field->optional));
                WriteLineIndent("result.store(key, value)");
            }
        }
        WriteLineIndent("result");
        Indent(-1);
        WriteLineIndent("end");

        // Generate struct from_json method
        WriteLine();
        WriteLineIndent("# Get struct from JSON");
        WriteLineIndent("def self.from_json(json)");
        Indent(1);
        WriteLineIndent("__from_json_map__(JSON.parse(json))");
        Indent(-1);
        WriteLineIndent("end");

        // Generate struct __from_json_map__ method
        WriteLine();
        WriteLineIndent("# Get struct map from JSON (internal method)");
        WriteLineIndent("def self.__from_json_map__(json)");
        Indent(1);
        WriteLineIndent("result = " + struct_name + ".new");
        if (s->base && !s->base->empty())
            WriteLineIndent("result.method(:initialize_copy).super_method.call(" + struct_name + ".method(:__from_json_map__).super_method.call(json))");
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLineIndent("value = json.fetch('" + *field->name + "', nil)");
                if (field->array || field->vector || field->list)
                    WriteLineIndent("result." + *field->name + " = value.map { |item| " + ConvertValueFromJson(*field->type, "item", field->optional) + " }");
                else if (field->set)
                    WriteLineIndent("result." + *field->name + " = (value.map { |item| " + ConvertValueFromJson(*field->type, "item", field->optional) + " }).to_set");
                else if (field->map || field->hash)
                    WriteLineIndent("result." + *field->name + " = (value.map { |key, value| [" + ConvertKeyFromJson(*field->key, "key", false) + ", " + ConvertValueFromJson(*field->type, "value", field->optional) + "] }).to_h");
                else
                    WriteLineIndent("result." + *field->name + " = " + ConvertValueFromJson(*field->type, "value", field->optional));
            }
        }
        WriteLineIndent("result");
        Indent(-1);
        WriteLineIndent("end");
    }

    // Generate struct FBE type property
    WriteLine();
    WriteLineIndent("# Get the FBE type");
    WriteLineIndent("def fbe_type");
    Indent(1);
    WriteLineIndent("TYPE");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct FBE type
    WriteLine();
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("TYPE = " + base_type + "::TYPE");
    else
        WriteLineIndent("TYPE = " + std::to_string(s->type));

    // Generate struct end
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model
    GenerateStructFieldModel(s);

    // Generate struct model
    GenerateStructModel(s);

    // Generate struct final models
    if (Final())
    {
        GenerateStructFinalModel(s);
        GenerateStructModelFinal(s);
    }
}

void GeneratorRuby::GenerateStructFieldModel(const std::shared_ptr<StructType>& s)
{
    std::string struct_name = ConvertTitle(*s->name);
    std::string base_type = (s->base && !s->base->empty()) ? ConvertTypeFieldName(*s->base, false) : "";

    // Generate struct field model begin
    WriteLine();
    WriteLineIndent("# noinspection RubyResolve, RubyScope, RubyTooManyInstanceVariablesInspection, RubyTooManyMethodsInspection");
    WriteLineIndent("class FieldModel" + struct_name + " < FBE::FieldModel");
    Indent(1);

    // Generate struct field model constructor
    WriteLineIndent("def initialize(buffer, offset)");
    Indent(1);
    WriteLineIndent("super(buffer, offset)");
    std::string prev_offset("4");
    std::string prev_size("4");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("@_parent = " + base_type + ".new(self.buffer, " + prev_offset + " + " + prev_size + ")");
        prev_offset = "@_parent.fbe_offset";
        prev_size = "@_parent.fbe_body - 4 - 4";
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("@_" + *field->name + " = " + ConvertTypeFieldInitialization(*field, prev_offset + " + " + prev_size, false));
            prev_offset = "@_" + *field->name + ".fbe_offset";
            prev_size = "@_" + *field->name + ".fbe_size";
        }
    }
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model accessors
    if (s->base && !s->base->empty())
    {
        WriteLine();
        WriteLineIndent("def parent");
        Indent(1);
        WriteLineIndent("@_parent");
        Indent(-1);
        WriteLineIndent("end");
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLine();
            WriteLineIndent("def " + *field->name);
            Indent(1);
            WriteLineIndent("@_" + *field->name);
            Indent(-1);
            WriteLineIndent("end");
        }
    }

    // Generate struct field model FBE properties
    WriteLine();
    WriteLineIndent("# Get the field size");
    WriteLineIndent("def fbe_size");
    Indent(1);
    WriteLineIndent("4");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Get the field body size");
    WriteLineIndent("def fbe_body");
    Indent(1);
    WriteLineIndent("4 + 4 \\");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbe_body - 4 - 4 \\");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbe_size \\");
    Indent(-1);
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Get the field extra size");
    WriteLineIndent("def fbe_extra");
    Indent(1);
    WriteLineIndent("if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_offset = read_uint32(fbe_offset)");
    WriteLineIndent("if (fbe_struct_offset == 0) || ((@_buffer.offset + fbe_struct_offset + 4) > @_buffer.size)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("@_buffer.shift(fbe_struct_offset)");
    WriteLine();
    WriteLineIndent("fbe_result = fbe_body \\");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbe_extra \\");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbe_extra \\");
    Indent(-1);
    WriteLine();
    WriteLineIndent("@_buffer.unshift(fbe_struct_offset)");
    WriteLine();
    WriteLineIndent("fbe_result");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Get the field type");
    WriteLineIndent("def fbe_type");
    Indent(1);
    WriteLineIndent("TYPE");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model type
    WriteLine();
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("TYPE = " + base_type + "::TYPE");
    else
        WriteLineIndent("TYPE = " + std::to_string(s->type));

    // Generate struct field model verify() method
    WriteLine();
    WriteLineIndent("# Is the struct value valid?");
    WriteLineIndent("def valid?");
    Indent(1);
    WriteLineIndent("verify");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Check if the struct value is valid");
    WriteLineIndent("def verify(fbe_verify_type = true)");
    Indent(1);
    WriteLineIndent("if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size");
    Indent(1);
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_offset = read_uint32(fbe_offset)");
    WriteLineIndent("if (fbe_struct_offset == 0) || ((@_buffer.offset + fbe_struct_offset + 4 + 4) > @_buffer.size)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_size = read_uint32(fbe_struct_offset)");
    WriteLineIndent("if fbe_struct_size < (4 + 4)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_type = read_uint32(fbe_struct_offset + 4)");
    WriteLineIndent("if fbe_verify_type && (fbe_struct_type != fbe_type)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("@_buffer.shift(fbe_struct_offset)");
    WriteLineIndent("fbe_result = verify_fields(fbe_struct_size)");
    WriteLineIndent("@_buffer.unshift(fbe_struct_offset)");
    WriteLineIndent("fbe_result");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model verify_fields() method
    WriteLine();
    WriteLineIndent("# Check if the struct fields are valid");
    WriteLineIndent("def verify_fields(fbe_struct_size)");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_size = 4 + 4");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if (fbe_current_size + parent.fbe_body - 4 - 4) > fbe_struct_size");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("end");
            WriteLineIndent("unless parent.verify_fields(fbe_struct_size)");
            Indent(1);
            WriteLineIndent("return false");
            Indent(-1);
            WriteLineIndent("end");
            WriteLineIndent("# noinspection RubyUnusedLocalVariable");
            WriteLineIndent("fbe_current_size += parent.fbe_body - 4 - 4");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if (fbe_current_size + " + *field->name + ".fbe_size) > fbe_struct_size");
                Indent(1);
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("unless " + *field->name + ".verify");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("# noinspection RubyUnusedLocalVariable");
                WriteLineIndent("fbe_current_size += " + *field->name + ".fbe_size");
            }
        }
        WriteLine();
    }
    WriteLineIndent("true");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model get_begin() method
    WriteLine();
    WriteLineIndent("# Get the struct value (begin phase)");
    WriteLineIndent("def get_begin");
    Indent(1);
    WriteLineIndent("if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_offset = read_uint32(fbe_offset)");
    WriteLineIndent("if (fbe_struct_offset == 0) || ((@_buffer.offset + fbe_struct_offset + 4 + 4) > @_buffer.size)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_size = read_uint32(fbe_struct_offset)");
    WriteLineIndent("if fbe_struct_size < (4 + 4)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("@_buffer.shift(fbe_struct_offset)");
    WriteLineIndent("fbe_struct_offset");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model get_end() method
    WriteLine();
    WriteLineIndent("# Get the struct value (end phase)");
    WriteLineIndent("def get_end(fbe_begin)");
    Indent(1);
    WriteLineIndent("@_buffer.unshift(fbe_begin)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model get() method
    WriteLine();
    WriteLineIndent("# Get the struct value");
    WriteLineIndent("def get(fbe_value = " + struct_name + ".new)");
    Indent(1);
    WriteLineIndent("fbe_begin = get_begin");
    WriteLineIndent("if fbe_begin == 0");
    Indent(1);
    WriteLineIndent("return fbe_value");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_size = read_uint32(0)");
    WriteLineIndent("get_fields(fbe_value, fbe_struct_size)");
    WriteLineIndent("get_end(fbe_begin)");
    WriteLineIndent("fbe_value");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model get_fields() method
    WriteLine();
    WriteLineIndent("# Get the struct fields values");
    WriteLineIndent("def get_fields(fbe_value, fbe_struct_size)");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_size = 4 + 4");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if (fbe_current_size + parent.fbe_body - 4 - 4) <= fbe_struct_size");
            Indent(1);
            WriteLineIndent("parent.get_fields(fbe_value, fbe_struct_size)");
            Indent(-1);
            WriteLineIndent("end");
            WriteLineIndent("# noinspection RubyUnusedLocalVariable");
            WriteLineIndent("fbe_current_size += parent.fbe_body - 4 - 4");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if (fbe_current_size + " + *field->name + ".fbe_size) <= fbe_struct_size");
                Indent(1);
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent(*field->name + ".get(fbe_value." + *field->name + ")");
                else
                    WriteLineIndent("fbe_value." + *field->name + " = " + *field->name + ".get" + (field->value ? ("(" + ConvertConstant(*field->type, *field->value, field->optional) + ")") : ""));
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbe_value." + *field->name + ".clear");
                else
                    WriteLineIndent("fbe_value." + *field->name + " = " + ConvertDefault(*field));
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("# noinspection RubyUnusedLocalVariable");
                WriteLineIndent("fbe_current_size += " + *field->name + ".fbe_size");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model set_begin() method
    WriteLine();
    WriteLineIndent("# Set the struct value (begin phase)");
    WriteLineIndent("def set_begin");
    Indent(1);
    WriteLineIndent("if (@_buffer.offset + fbe_offset + fbe_size) > @_buffer.size");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_size = fbe_body");
    WriteLineIndent("fbe_struct_offset = @_buffer.allocate(fbe_struct_size) - @_buffer.offset");
    WriteLineIndent("if (fbe_struct_offset <= 0) || ((@_buffer.offset + fbe_struct_offset + fbe_struct_size) > @_buffer.size)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("write_uint32(fbe_offset, fbe_struct_offset)");
    WriteLineIndent("write_uint32(fbe_struct_offset, fbe_struct_size)");
    WriteLineIndent("write_uint32(fbe_struct_offset + 4, fbe_type)");
    WriteLine();
    WriteLineIndent("@_buffer.shift(fbe_struct_offset)");
    WriteLineIndent("fbe_struct_offset");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model set_end() method
    WriteLine();
    WriteLineIndent("# Set the struct value (end phase)");
    WriteLineIndent("def set_end(fbe_begin)");
    Indent(1);
    WriteLineIndent("@_buffer.unshift(fbe_begin)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model set() method
    WriteLine();
    WriteLineIndent("# Set the struct value");
    WriteLineIndent("def set(fbe_value)");
    Indent(1);
    WriteLineIndent("fbe_begin = set_begin");
    WriteLineIndent("if fbe_begin == 0");
    Indent(1);
    WriteLineIndent("return");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("set_fields(fbe_value)");
    WriteLineIndent("set_end(fbe_begin)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model set_fields() method
    WriteLine();
    WriteLineIndent("# Set the struct fields values");
    WriteLineIndent("def set_fields(fbe_value)");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        if (s->base && !s->base->empty())
            WriteLineIndent("parent.set_fields(fbe_value)");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent(*field->name + ".set(fbe_value." + *field->name + ")");
    }
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct field model end
    Indent(-1);
    WriteLineIndent("end");
}

void GeneratorRuby::GenerateStructModel(const std::shared_ptr<StructType>& s)
{
    std::string struct_name = ConvertTitle(*s->name);

    // Generate struct model begin
    WriteLine();
    WriteLineIndent("# Fast Binary Encoding " + struct_name + " model");
    WriteLineIndent("class " + struct_name + "Model < FBE::Model");
    Indent(1);

    // Generate struct model constructor
    WriteLineIndent("def initialize(buffer = FBE::WriteBuffer.new)");
    Indent(1);
    WriteLineIndent("super(buffer)");
    WriteLineIndent("@_model = FieldModel" + struct_name + ".new(self.buffer, 4)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model accessor
    WriteLine();
    WriteLineIndent("def model");
    Indent(1);
    WriteLineIndent("@_model");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model FBE properties
    WriteLine();
    WriteLineIndent("# Get the model size");
    WriteLineIndent("def fbe_size");
    Indent(1);
    WriteLineIndent("@_model.fbe_size + @_model.fbe_extra");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Get the model type");
    WriteLineIndent("def fbe_type");
    Indent(1);
    WriteLineIndent("TYPE");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model type
    WriteLine();
    WriteLineIndent("TYPE = FieldModel" + struct_name + "::TYPE");

    // Generate struct model verify() method
    WriteLine();
    WriteLineIndent("# Is the struct value valid?");
    WriteLineIndent("def valid?");
    Indent(1);
    WriteLineIndent("verify");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Check if the struct value is valid");
    WriteLineIndent("def verify");
    Indent(1);
    WriteLineIndent("if (buffer.offset + @_model.fbe_offset - 4) > buffer.size");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_full_size = read_uint32(@_model.fbe_offset - 4)");
    WriteLineIndent("if fbe_full_size < @_model.fbe_size");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("@_model.verify");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model create_begin() method
    WriteLine();
    WriteLineIndent("# Create a new model (begin phase)");
    WriteLineIndent("def create_begin");
    Indent(1);
    WriteLineIndent("buffer.allocate(4 + @_model.fbe_size)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model create_end() method
    WriteLine();
    WriteLineIndent("# Create a new model (end phase)");
    WriteLineIndent("def create_end(fbe_begin)");
    Indent(1);
    WriteLineIndent("fbe_end = buffer.size");
    WriteLineIndent("fbe_full_size = fbe_end - fbe_begin");
    WriteLineIndent("write_uint32(@_model.fbe_offset - 4, fbe_full_size)");
    WriteLineIndent("fbe_full_size");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model serialize() method
    WriteLine();
    WriteLineIndent("# Serialize the struct value");
    WriteLineIndent("def serialize(value)");
    Indent(1);
    WriteLineIndent("fbe_begin = create_begin");
    WriteLineIndent("@_model.set(value)");
    WriteLineIndent("create_end(fbe_begin)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model deserialize() methods
    WriteLine();
    WriteLineIndent("# Deserialize the struct value");
    WriteLineIndent("def deserialize(value = " + struct_name + ".new)");
    Indent(1);
    WriteLineIndent("if (buffer.offset + @_model.fbe_offset - 4) > buffer.size");
    Indent(1);
    WriteLineIndent("[" + struct_name + ".new, 0]");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_full_size = read_uint32(@_model.fbe_offset - 4)");
    WriteLineIndent("if fbe_full_size < @_model.fbe_size");
    Indent(1);
    WriteLineIndent("[" + struct_name + ".new, 0]");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("@_model.get(value)");
    WriteLineIndent("[value, fbe_full_size]");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model next() method
    WriteLine();
    WriteLineIndent("# Move to the next struct value");
    WriteLineIndent("def next(prev)");
    Indent(1);
    WriteLineIndent("@_model.fbe_shift(prev)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model end
    Indent(-1);
    WriteLineIndent("end");
}

void GeneratorRuby::GenerateStructFinalModel(const std::shared_ptr<StructType>& s)
{
    std::string struct_name = ConvertTitle(*s->name);
    std::string base_type = (s->base && !s->base->empty()) ? ConvertTypeFieldName(*s->base, true) : "";

    // Generate struct final model begin
    WriteLine();
    WriteLineIndent("# noinspection RubyResolve, RubyScope, RubyTooManyInstanceVariablesInspection, RubyTooManyMethodsInspection");
    WriteLineIndent("class FinalModel" + struct_name + " < FBE::FinalModel");
    Indent(1);

    // Generate struct final model constructor
    WriteLineIndent("def initialize(buffer, offset)");
    Indent(1);
    WriteLineIndent("super(buffer, offset)");
    if (s->base && !s->base->empty())
        WriteLineIndent("@_parent = " + base_type + ".new(self.buffer, 0)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("@_" + *field->name + " = " + ConvertTypeFieldInitialization(*field, "0", true));
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct final model accessors
    if (s->base && !s->base->empty())
    {
        WriteLine();
        WriteLineIndent("def parent");
        Indent(1);
        WriteLineIndent("@_parent");
        Indent(-1);
        WriteLineIndent("end");
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLine();
            WriteLineIndent("def " + *field->name);
            Indent(1);
            WriteLineIndent("@_" + *field->name);
            Indent(-1);
            WriteLineIndent("end");
        }
    }

    // Generate struct final model FBE properties
    WriteLine();
    WriteLineIndent("# Get the allocation size");
    WriteLineIndent("def fbe_allocation_size(fbe_value)");
    Indent(1);
    WriteLineIndent("0 \\");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbe_allocation_size(fbe_value) \\");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbe_allocation_size(fbe_value." + *field->name + ") \\");
    Indent(-1);
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Get the final type");
    WriteLineIndent("def fbe_type");
    Indent(1);
    WriteLineIndent("TYPE");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct final model type
    WriteLine();
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("TYPE = " + base_type + "::TYPE");
    else
        WriteLineIndent("TYPE = " + std::to_string(s->type));

    // Generate struct final model verify() method
    WriteLine();
    WriteLineIndent("# Is the struct value valid?");
    WriteLineIndent("def valid?");
    Indent(1);
    WriteLineIndent("verify");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Check if the struct value is valid");
    WriteLineIndent("def verify");
    Indent(1);
    WriteLineIndent("@_buffer.shift(fbe_offset)");
    WriteLineIndent("fbe_result = verify_fields");
    WriteLineIndent("@_buffer.unshift(fbe_offset)");
    WriteLineIndent("fbe_result");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct final model verify_fields() method
    WriteLine();
    WriteLineIndent("# Check if the struct fields are valid");
    WriteLineIndent("def verify_fields");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_offset = 0");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbe_offset = fbe_current_offset");
            WriteLineIndent("fbe_field_size = parent.verify_fields");
            WriteLineIndent("if fbe_field_size == FBE::Integer::MAX");
            Indent(1);
            WriteLineIndent("return FBE::Integer::MAX");
            Indent(-1);
            WriteLineIndent("end");
            WriteLineIndent("fbe_current_offset += fbe_field_size");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbe_offset = fbe_current_offset");
                WriteLineIndent("fbe_field_size = " + *field->name + ".verify");
                WriteLineIndent("if fbe_field_size == FBE::Integer::MAX");
                Indent(1);
                WriteLineIndent("return FBE::Integer::MAX");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("fbe_current_offset += fbe_field_size");
            }
        }
        WriteLine();
        WriteLineIndent("# noinspection RubyUnnecessaryReturnValue");
        WriteLineIndent("fbe_current_offset");
    }
    else
        WriteLineIndent("0");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct final model get() method
    WriteLine();
    WriteLineIndent("# Get the struct value");
    WriteLineIndent("def get(fbe_value = " + struct_name + ".new)");
    Indent(1);
    WriteLineIndent("@_buffer.shift(fbe_offset)");
    WriteLineIndent("fbe_size = get_fields(fbe_value)");
    WriteLineIndent("@_buffer.unshift(fbe_offset)");
    WriteLineIndent("[fbe_value, fbe_size]");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct final model get_fields() method
    WriteLine();
    WriteLineIndent("# Get the struct fields values");
    WriteLineIndent("def get_fields(fbe_value)");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_offset = 0");
        WriteLineIndent("fbe_current_size = 0");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbe_offset = fbe_current_offset");
            WriteLineIndent("fbe_result = parent.get_fields(fbe_value)");
            WriteLineIndent("# noinspection RubyUnusedLocalVariable");
            WriteLineIndent("fbe_current_offset += fbe_result");
            WriteLineIndent("fbe_current_size += fbe_result");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbe_offset = fbe_current_offset");
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbe_result = " + *field->name + ".get(fbe_value." + *field->name + ")");
                else
                {
                    WriteLineIndent("fbe_result = " + *field->name + ".get");
                    WriteLineIndent("fbe_value." + *field->name + " = fbe_result[0]");
                }
                WriteLineIndent("# noinspection RubyUnusedLocalVariable");
                WriteLineIndent("fbe_current_offset += fbe_result[1]");
                WriteLineIndent("fbe_current_size += fbe_result[1]");
            }
        }
        WriteLine();
        WriteLineIndent("# noinspection RubyUnnecessaryReturnValue");
        WriteLineIndent("fbe_current_size");
    }
    else
        WriteLineIndent("0");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct final model set() method
    WriteLine();
    WriteLineIndent("# Set the struct value");
    WriteLineIndent("def set(fbe_value)");
    Indent(1);
    WriteLineIndent("@_buffer.shift(fbe_offset)");
    WriteLineIndent("fbe_size = set_fields(fbe_value)");
    WriteLineIndent("@_buffer.unshift(fbe_offset)");
    WriteLineIndent("fbe_size");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct final model set_fields() method
    WriteLine();
    WriteLineIndent("# Set the struct fields values");
    WriteLineIndent("def set_fields(fbe_value)");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_offset = 0");
        WriteLineIndent("fbe_current_size = 0");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbe_offset = fbe_current_offset");
            WriteLineIndent("fbe_field_size = parent.set_fields(fbe_value)");
            WriteLineIndent("# noinspection RubyUnusedLocalVariable");
            WriteLineIndent("fbe_current_offset += fbe_field_size");
            WriteLineIndent("fbe_current_size += fbe_field_size");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbe_offset = fbe_current_offset");
                WriteLineIndent("fbe_field_size = " + *field->name + ".set(fbe_value." + *field->name + ")");
                WriteLineIndent("# noinspection RubyUnusedLocalVariable");
                WriteLineIndent("fbe_current_offset += fbe_field_size");
                WriteLineIndent("fbe_current_size += fbe_field_size");
            }
        }
        WriteLine();
        WriteLineIndent("# noinspection RubyUnnecessaryReturnValue");
        WriteLineIndent("fbe_current_size");
    }
    else
        WriteLineIndent("0");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct final model end
    Indent(-1);
    WriteLineIndent("end");
}

void GeneratorRuby::GenerateStructModelFinal(const std::shared_ptr<StructType>& s)
{
    std::string struct_name = ConvertTitle(*s->name);

    // Generate struct model final begin
    WriteLine();
    WriteLineIndent("# Fast Binary Encoding " + struct_name + " final model");
    WriteLineIndent("class " + struct_name + "FinalModel < FBE::Model");
    Indent(1);

    // Generate struct model final constructor
    WriteLineIndent("def initialize(buffer = FBE::WriteBuffer.new)");
    Indent(1);
    WriteLineIndent("super(buffer)");
    WriteLineIndent("@_model = FinalModel" + struct_name + ".new(self.buffer, 8)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model final FBE properties
    WriteLine();
    WriteLineIndent("# Get the model type");
    WriteLineIndent("def fbe_type");
    Indent(1);
    WriteLineIndent("TYPE");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model type
    WriteLine();
    WriteLineIndent("TYPE = FinalModel" + struct_name + "::TYPE");

    // Generate struct model final verify() method
    WriteLine();
    WriteLineIndent("# Is the struct value valid?");
    WriteLineIndent("def valid?");
    Indent(1);
    WriteLineIndent("verify");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("# Check if the struct value is valid");
    WriteLineIndent("def verify");
    Indent(1);
    WriteLineIndent("if (buffer.offset + @_model.fbe_offset) > buffer.size");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_size = read_uint32(@_model.fbe_offset - 8)");
    WriteLineIndent("fbe_struct_type = read_uint32(@_model.fbe_offset - 4)");
    WriteLineIndent("if (fbe_struct_size <= 0) or (fbe_struct_type != fbe_type)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("(8 + @_model.verify) == fbe_struct_size");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model final serialize() method
    WriteLine();
    WriteLineIndent("# Serialize the struct value");
    WriteLineIndent("def serialize(value)");
    Indent(1);
    WriteLineIndent("fbe_initial_size = buffer.size");
    WriteLine();
    WriteLineIndent("fbe_struct_type = fbe_type");
    WriteLineIndent("fbe_struct_size = 8 + @_model.fbe_allocation_size(value)");
    WriteLineIndent("fbe_struct_offset = buffer.allocate(fbe_struct_size) - buffer.offset");
    WriteLineIndent("if (buffer.offset + fbe_struct_offset + fbe_struct_size) > buffer.size");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_size = 8 + @_model.set(value)");
    WriteLineIndent("buffer.resize(fbe_initial_size + fbe_struct_size)");
    WriteLine();
    WriteLineIndent("write_uint32(@_model.fbe_offset - 8, fbe_struct_size)");
    WriteLineIndent("write_uint32(@_model.fbe_offset - 4, fbe_struct_type)");
    WriteLine();
    WriteLineIndent("fbe_struct_size");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model final deserialize() methods
    WriteLine();
    WriteLineIndent("# Deserialize the struct value");
    WriteLineIndent("def deserialize(value = " + struct_name + ".new)");
    Indent(1);
    WriteLineIndent("if (buffer.offset + @_model.fbe_offset) > buffer.size");
    Indent(1);
    WriteLineIndent("[" + struct_name + ".new, 0]");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_struct_size = read_uint32(@_model.fbe_offset - 8)");
    WriteLineIndent("fbe_struct_type = read_uint32(@_model.fbe_offset - 4)");
    WriteLineIndent("if (fbe_struct_size <= 0) || (fbe_struct_type != fbe_type)");
    Indent(1);
    WriteLineIndent("[" + struct_name + ".new, 8]");
    Indent(-1);
    WriteLineIndent("end");
    WriteLine();
    WriteLineIndent("fbe_result = @_model.get(value)");
    WriteLineIndent("[fbe_result[0], (8 + fbe_result[1])]");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model final next() method
    WriteLine();
    WriteLineIndent("# Move to the next struct value");
    WriteLineIndent("def next(prev)");
    Indent(1);
    WriteLineIndent("@_model.fbe_shift(prev)");
    Indent(-1);
    WriteLineIndent("end");

    // Generate struct model final end
    Indent(-1);
    WriteLineIndent("end");
}

void GeneratorRuby::GenerateProtocolVersion(const std::shared_ptr<Package>& p)
{
    std::string package = ConvertTitle(*p->name);

    // Generate protocol version class
    WriteLine();
    WriteLineIndent("# Fast Binary Encoding " + package + " protocol version");
    WriteLineIndent("class ProtocolVersion");
    Indent(1);
    WriteLineIndent("# Protocol major version");
    WriteLineIndent("MAJOR = " + std::to_string(p->version->major));
    WriteLineIndent("# Protocol minor version");
    WriteLineIndent("MINOR = " + std::to_string(p->version->minor));
    Indent(-1);
    WriteLineIndent("end");
}

void GeneratorRuby::GenerateSender(const std::shared_ptr<Package>& p, bool final)
{
    std::string package = ConvertTitle(*p->name);
    std::string sender = (final ? "FinalSender" : "Sender");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate sender begin
    WriteLine();
    if (final)
        WriteLineIndent("# Fast Binary Encoding " + package + " final sender");
    else
        WriteLineIndent("# Fast Binary Encoding " + package + " sender");
    WriteLineIndent("# noinspection RubyResolve, RubyScope, RubyTooManyInstanceVariablesInspection, RubyTooManyMethodsInspection");
    WriteLineIndent("class " + sender + " < FBE::Sender");
    Indent(1);

    // Generate sender constructor
    WriteLineIndent("def initialize(buffer = FBE::WriteBuffer.new)");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*import) + "_sender = " + ConvertTitle(*import) + "::" + sender + ".new(self.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*s->name) + "_model = " + ConvertTitle(*s->name) + model + ".new(self.buffer)");
    }
    Indent(-1);
    WriteLineIndent("end");

    // Generate imported senders accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("# Imported senders");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_sender");
            Indent(1);
            WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*import) + "_sender");
            Indent(-1);
            WriteLineIndent("end");
        }
    }

    // Generate sender models accessors
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("# Sender models accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*s->name) + "_model");
                Indent(1);
                WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*s->name) + "_model");
                Indent(-1);
                WriteLineIndent("end");
            }
        }
    }

    // Generate sender methods
    WriteLine();
    WriteLineIndent("# Send methods");
    WriteLine();
    WriteLineIndent("def send(value)");
    Indent(1);
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("if value.is_a?(" + ConvertTitle(*s->name) + ") && (value.fbe_type == " + CppCommon::StringUtils::ToLower(*s->name) + "_model.fbe_type)");
                Indent(1);
                WriteLineIndent("return send_" + CppCommon::StringUtils::ToLower(*s->name) + "(value)");
                Indent(-1);
                WriteLineIndent("end");
            }
        }
    }
    if (p->import)
    {
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = @_" + CppCommon::StringUtils::ToLower(*import) + "_sender.send(value)");
            WriteLineIndent("if result > 0");
            Indent(1);
            WriteLineIndent("return result");
            Indent(-1);
            WriteLineIndent("end");
        }
    }
    WriteLineIndent("0");
    Indent(-1);
    WriteLineIndent("end");
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("def send_" + CppCommon::StringUtils::ToLower(*s->name) + "(value)");
                Indent(1);
                WriteLineIndent("# Serialize the value into the FBE stream");
                WriteLineIndent("serialized = " + CppCommon::StringUtils::ToLower(*s->name) + "_model.serialize(value)");
                WriteLineIndent("raise RuntimeError, \"" + package + "." + ConvertTitle(*s->name) + " serialization failed!\" if serialized <= 0");
                WriteLineIndent("raise RuntimeError, \"" + package + "." + ConvertTitle(*s->name) + " validation failed!\" unless " + CppCommon::StringUtils::ToLower(*s->name) + "_model.verify");
                WriteLine();
                WriteLineIndent("# Log the value");
                WriteLineIndent("if logging?");
                Indent(1);
                WriteLineIndent("message = value.to_s");
                WriteLineIndent("on_send_log(message)");
                Indent(-1);
                WriteLineIndent("end");
                WriteLine();
                WriteLineIndent("# Send the serialized value");
                WriteLineIndent("send_serialized(serialized)");
                Indent(-1);
                WriteLineIndent("end");
            }
        }
    }

    // Generate sender message handler
    WriteLine();
    WriteLineIndent("protected");
    WriteLine();
    WriteLineIndent("# Send message handler");
    WriteLineIndent("def on_send(buffer, offset, size)");
    Indent(1);
    WriteLineIndent("raise NotImplementedError, \"" + package + ".Sender.on_send() not implemented!\"");
    Indent(-1);
    WriteLineIndent("end");

    // Generate sender end
    Indent(-1);
    WriteLineIndent("end");
}

void GeneratorRuby::GenerateReceiver(const std::shared_ptr<Package>& p, bool final)
{
    std::string package = ConvertTitle(*p->name);
    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate receiver begin
    WriteLine();
    if (final)
        WriteLineIndent("# Fast Binary Encoding " + package + " final receiver");
    else
        WriteLineIndent("# Fast Binary Encoding " + package + " receiver");
    WriteLineIndent("# noinspection RubyResolve, RubyScope, RubyTooManyInstanceVariablesInspection, RubyTooManyMethodsInspection");
    WriteLineIndent("class " + receiver + " < FBE::Receiver");
    Indent(1);

    // Generate receiver constructor
    WriteLineIndent("def initialize(buffer = FBE::WriteBuffer.new)");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*import) + "_receiver = " + ConvertTitle(*import) + "::" + receiver + ".new(self.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*s->name) + "_value = " + ConvertTitle(*s->name) + ".new");
                WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*s->name) + "_model = " + ConvertTitle(*s->name) + model + ".new");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("end");

    // Generate imported receiver accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("# Imported receivers");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_receiver");
            Indent(1);
            WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*import) + "_receiver");
            Indent(-1);
            WriteLineIndent("end");
            WriteLine();
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_receiver=(receiver)");
            Indent(1);
            WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*import) + "_receiver = receiver");
            Indent(-1);
            WriteLineIndent("end");
        }
    }

    WriteLine();
    WriteLineIndent("protected");

    // Generate receiver handlers
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("# Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("# noinspection RubyUnusedLocalVariable");
                WriteLineIndent("def on_receive_" + CppCommon::StringUtils::ToLower(*s->name) + "(value)");
                WriteLineIndent("end");
            }
        }
    }

    WriteLine();
    WriteLineIndent("public");

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate receiver message handler
    WriteLine();
    WriteLineIndent("def on_receive(type, buffer, offset, size)");
    Indent(1);
    if (p->body && messages)
    {
        WriteLineIndent("case type");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("when " + ConvertTitle(*s->name) + model + "::TYPE");
                Indent(1);
                WriteLineIndent("# Deserialize the value from the FBE stream");
                WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*s->name) + "_model.attach_buffer(buffer, offset)");
                WriteLineIndent("unless @_" + CppCommon::StringUtils::ToLower(*s->name) + "_model.verify");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("_, deserialized = @_" + CppCommon::StringUtils::ToLower(*s->name) + "_model.deserialize(@_" + CppCommon::StringUtils::ToLower(*s->name) + "_value)");
                WriteLineIndent("if deserialized <= 0");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLine();
                WriteLineIndent("# Log the value");
                WriteLineIndent("if logging?");
                Indent(1);
                WriteLineIndent("message = @_" + CppCommon::StringUtils::ToLower(*s->name) + "_value.to_s");
                WriteLineIndent("on_receive_log(message)");
                Indent(-1);
                WriteLineIndent("end");
                WriteLine();
                WriteLineIndent("# Call receive handler with deserialized value");
                WriteLineIndent("on_receive_" + CppCommon::StringUtils::ToLower(*s->name) + "(@_" + CppCommon::StringUtils::ToLower(*s->name) + "_value)");
                WriteLineIndent("true");
                Indent(-1);
            }
        }
        WriteLineIndent("else");
        Indent(1);
        WriteLineIndent("# Do nothing here...");
        Indent(-1);
        WriteLineIndent("end");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if !" + CppCommon::StringUtils::ToLower(*import) + "_receiver.nil? && " + CppCommon::StringUtils::ToLower(*import) + "_receiver.on_receive(type, buffer, offset, size)");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("end");
        }
    }
    WriteLine();
    WriteLineIndent("false");
    Indent(-1);
    WriteLineIndent("end");

    // Generate receiver end
    Indent(-1);
    WriteLineIndent("end");
}

void GeneratorRuby::GenerateProxy(const std::shared_ptr<Package>& p, bool final)
{
    std::string package = ConvertTitle(*p->name);
    std::string proxy = (final ? "FinalProxy" : "Proxy");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate proxy begin
    WriteLine();
    if (final)
        WriteLineIndent("# Fast Binary Encoding " + package + " final proxy");
    else
        WriteLineIndent("# Fast Binary Encoding " + package + " proxy");
    WriteLineIndent("# noinspection RubyResolve, RubyScope, RubyTooManyInstanceVariablesInspection, RubyTooManyMethodsInspection");
    WriteLineIndent("class " + proxy + " < FBE::Receiver");
    Indent(1);

    // Generate proxy constructor
    WriteLineIndent("def initialize(buffer = FBE::WriteBuffer.new)");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*import) + "_proxy = " + ConvertTitle(*import) + "::" + proxy + ".new(self.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*s->name) + "_model = " + ConvertTitle(*s->name) + model + ".new");
    }
    Indent(-1);
    WriteLineIndent("end");

    // Generate imported proxy accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("# Imported proxy");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_proxy");
            Indent(1);
            WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*import) + "_proxy");
            Indent(-1);
            WriteLineIndent("end");
            WriteLine();
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_proxy=(proxy)");
            Indent(1);
            WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*import) + "_proxy = proxy");
            Indent(-1);
            WriteLineIndent("end");
        }
    }

    WriteLine();
    WriteLineIndent("protected");

    // Generate proxy handlers
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("# Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("# noinspection RubyUnusedLocalVariable");
                WriteLineIndent("def on_proxy_" + CppCommon::StringUtils::ToLower(*s->name) + "(model, type, buffer, offset, size)");
                WriteLineIndent("end");
            }
        }
    }

    WriteLine();
    WriteLineIndent("public");

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate proxy message handler
    WriteLine();
    WriteLineIndent("def on_receive(type, buffer, offset, size)");
    Indent(1);
    if (p->body && messages)
    {
        WriteLineIndent("case type");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("when " + ConvertTitle(*s->name) + model + "::TYPE");
                Indent(1);
                WriteLineIndent("# Attach the FBE stream to the proxy model");
                WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*s->name) + "_model.attach_buffer(buffer, offset)");
                WriteLineIndent("unless @_" + CppCommon::StringUtils::ToLower(*s->name) + "_model.verify");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLine();
                WriteLineIndent("fbe_begin = @_" + CppCommon::StringUtils::ToLower(*s->name) + "_model.model.get_begin");
                WriteLineIndent("if fbe_begin == 0");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("end");
                WriteLineIndent("# Call proxy handler");
                WriteLineIndent("on_proxy_" + CppCommon::StringUtils::ToLower(*s->name) + "(@_" + CppCommon::StringUtils::ToLower(*s->name) + "_model, type, buffer, offset, size)");
                WriteLineIndent("@_" + CppCommon::StringUtils::ToLower(*s->name) + "_model.model.get_end(fbe_begin)");
                WriteLineIndent("true");
                Indent(-1);
            }
        }
        WriteLineIndent("else");
        Indent(1);
        WriteLineIndent("# Do nothing here...");
        Indent(-1);
        WriteLineIndent("end");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if !" + CppCommon::StringUtils::ToLower(*import) + "_proxy.nil? && " + CppCommon::StringUtils::ToLower(*import) + "_proxy.on_receive(type, buffer, offset, size)");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("end");
        }
    }
    WriteLine();
    WriteLineIndent("false");
    Indent(-1);
    WriteLineIndent("end");

    // Generate proxy end
    Indent(-1);
    WriteLineIndent("end");
}

bool GeneratorRuby::IsPrimitiveType(const std::string& type)
{
    return ((type == "bool") || (type == "byte") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double"));
}

bool GeneratorRuby::IsRubyType(const std::string& type)
{
    return IsPrimitiveType(type) || (type == "bytes") || (type == "decimal") || (type == "string") || (type == "timestamp") || (type == "uuid");
}

std::string GeneratorRuby::ConvertTitle(const std::string& type)
{
    std::vector<std::string> parts = CppCommon::StringUtils::Split(type, '.', false);

    if (!parts.empty())
    {
        std::string result = "";
        bool first = true;
        for (const auto& it : parts)
        {
            std::string value = CppCommon::StringUtils::ToTrim(it);
            value[0] = std::toupper(value[0]);
            result += (first ? "" : "::") + value;
            first = false;
        }
        return result;
    }

    std::string result = type;
    result[0] = std::toupper(result[0]);
    return result;
}

std::string GeneratorRuby::ConvertEnumSize(const std::string& type)
{
    if (type == "byte")
        return "1";
    else if (type == "char")
        return "1";
    else if (type == "wchar")
        return "4";
    else if (type == "int8")
        return "1";
    else if (type == "uint8")
        return "1";
    else if (type == "int16")
        return "2";
    else if (type == "uint16")
        return "2";
    else if (type == "int32")
        return "4";
    else if (type == "uint32")
        return "4";
    else if (type == "int64")
        return "8";
    else if (type == "uint64")
        return "8";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorRuby::ConvertEnumType(const std::string& type)
{
    if (type == "byte")
        return "byte";
    else if (type == "char")
        return "uint8";
    else if (type == "wchar")
        return "uint32";
    else if (type == "int8")
        return "int8";
    else if (type == "uint8")
        return "uint8";
    else if (type == "int16")
        return "int16";
    else if (type == "uint16")
        return "uint16";
    else if (type == "int32")
        return "int32";
    else if (type == "uint32")
        return "uint32";
    else if (type == "int64")
        return "int64";
    else if (type == "uint64")
        return "uint64";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorRuby::ConvertEnumConstant(const std::string& type, const std::string& value, bool flag)
{
    std::string prefix = flag ? "Flags.value(:" : "Enum.value(:";

    if (((type == "char") || (type == "wchar")) && CppCommon::StringUtils::StartsWith(value, "'"))
        return value + ".ord";
    else if (type.empty())
    {
        // Fill flags values
        std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);

        // Generate flags combination
        if (!flags.empty())
        {
            std::string result = "";
            bool first = true;
            for (const auto& it : flags)
            {
                result += std::string(first ? "" : "|") + prefix + CppCommon::StringUtils::ToTrim(it) + ")";
                first = false;
            }
            return result;
        }

        return prefix + CppCommon::StringUtils::ToTrim(value) + ")";
    }

    return value;
}

std::string GeneratorRuby::ConvertTypeName(const std::string& type, bool optional)
{
    return ConvertTitle(type);
}

std::string GeneratorRuby::ConvertTypeName(const StructField& field)
{
    if (field.array)
        return "Array";
    else if (field.vector)
        return "Array";
    else if (field.list)
        return "Array";
    else if (field.set)
        return "Set";
    else if (field.map)
        return "Hash";
    else if (field.hash)
        return "Hash";

    return ConvertTypeName(*field.type, field.optional);
}

std::string GeneratorRuby::ConvertTypeFieldName(const std::string& type, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (type == "bool")
        return "FBE::" + modelType + "ModelBool";
    else if (type == "byte")
        return "FBE::" + modelType + "ModelByte";
    else if (type == "bytes")
        return "FBE::" + modelType + "ModelBytes";
    else if (type == "char")
        return "FBE::" + modelType + "ModelChar";
    else if (type == "wchar")
        return "FBE::" + modelType + "ModelWChar";
    else if (type == "int8")
        return "FBE::" + modelType + "ModelInt8";
    else if (type == "uint8")
        return "FBE::" + modelType + "ModelUInt8";
    else if (type == "int16")
        return "FBE::" + modelType + "ModelInt16";
    else if (type == "uint16")
        return "FBE::" + modelType + "ModelUInt16";
    else if (type == "int32")
        return "FBE::" + modelType + "ModelInt32";
    else if (type == "uint32")
        return "FBE::" + modelType + "ModelUInt32";
    else if (type == "int64")
        return "FBE::" + modelType + "ModelInt64";
    else if (type == "uint64")
        return "FBE::" + modelType + "ModelUInt64";
    else if (type == "float")
        return "FBE::" + modelType + "ModelFloat";
    else if (type == "double")
        return "FBE::" + modelType + "ModelDouble";
    else if (type == "decimal")
        return "FBE::" + modelType + "ModelDecimal";
    else if (type == "timestamp")
        return "FBE::" + modelType + "ModelTimestamp";
    else if (type == "string")
        return "FBE::" + modelType + "ModelString";
    else if (type == "uuid")
        return "FBE::" + modelType + "ModelUUID";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        t.assign(type, pos + 1, type.size() - pos);
    }

    return ConvertTitle(ns) + modelType + "Model" + ConvertTypeName(t, false);
}

std::string GeneratorRuby::ConvertTypeFieldInitialization(const std::string& type, bool optional, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (optional)
        return "FBE::" + modelType + "ModelOptional.new(" + ConvertTypeFieldInitialization(type, false, offset, final)+ ", self.buffer, " + offset + ")";
    else
        return ConvertTypeFieldName(type, final) + ".new(self.buffer, " + offset + ")";
}

std::string GeneratorRuby::ConvertTypeFieldInitialization(const StructField& field, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
        return "FBE::" + modelType + "ModelArray.new(" + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", self.buffer, " + offset + ", " + std::to_string(field.N) + ")";
    else if (field.vector || field.list)
        return "FBE::" + modelType + "ModelVector.new(" + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", self.buffer, " + offset + ")";
    else if (field.set)
        return "FBE::" + modelType + "ModelSet.new(" + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", self.buffer, " + offset + ")";
    else if (field.map || field.hash)
        return "FBE::" + modelType + "ModelMap.new(" + ConvertTypeFieldInitialization(*field.key, false, offset, final) + ", " + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", self.buffer, " + offset + ")";
    else
        return ConvertTypeFieldInitialization(*field.type, field.optional, offset, final);
}

std::string GeneratorRuby::ConvertConstant(const std::string& type, const std::string& value, bool optional)
{
    if (value == "true")
        return "true";
    else if (value == "false")
        return "false";
    else if (value == "null")
        return "nil";
    else if (value == "min")
    {
        if ((type == "byte") || (type == "uint8") || (type == "uint16") || (type == "uint32") || (type == "uint64"))
            return "0";
        else if (type == "int8")
            return "-128";
        else if (type == "int16")
            return "-32768";
        else if (type == "int32")
            return "-2147483648";
        else if (type == "int64")
            return "-9223372036854775808";

        yyerror("Unsupported type " + type + " for 'min' constant");
        return "";
    }
    else if (value == "max")
    {
        if (type == "byte")
            return "255";
        else if (type == "int8")
            return "127";
        else if (type == "uint8")
            return "255";
        else if (type == "int16")
            return "32767";
        else if (type == "uint16")
            return "65535";
        else if (type == "int32")
            return "2147483647";
        else if (type == "uint32")
            return "4294967295";
        else if (type == "int64")
            return "9223372036854775807";
        else if (type == "uint64")
            return "18446744073709551615";

        yyerror("Unsupported type " + type + " for 'max' constant");
        return "";
    }
    else if (value == "epoch")
        return "Time.utc(1970)";
    else if (value == "utc")
        return "Time.now.utc";
    else if (value == "uuid0")
        return "UUIDTools::UUID.parse_int(0)";
    else if (value == "uuid1")
        return "UUIDTools::UUID.timestamp_create";
    else if (value == "uuid4")
        return "UUIDTools::UUID.random_create";

    if (type == "bool")
        return value + " != 0";
    else if ((type == "char") && !CppCommon::StringUtils::StartsWith(value, "'"))
        return value + ".chr";
    else if ((type == "wchar") && !CppCommon::StringUtils::StartsWith(value, "'"))
        return value + ".chr(Encoding::UTF_8)";
    else if (type == "decimal")
        return "BigDecimal('" + value + "')";
    else if (type == "uuid")
        return "UUIDTools::UUID.parse(" + value + ")";

    return value;
}

std::string GeneratorRuby::ConvertDefault(const std::string& type, bool optional)
{
    if (optional)
        return "nil";

    if (type == "bool")
        return "false";
    else if (type == "byte")
        return "0";
    else if (type == "bytes")
        return "''";
    else if ((type == "char") || (type == "wchar"))
        return "\"\\0\"";
    else if ((type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64"))
        return "0";
    else if ((type == "float") || (type == "double"))
        return "0.0";
    else if (type == "decimal")
        return "BigDecimal(0)";
    else if (type == "timestamp")
        return "Time.utc(1970)";
    else if (type == "string")
        return "''";
    else if (type == "uuid")
        return "UUIDTools::UUID.parse_int(0)";

    return ConvertTitle(type) + ".new";
}

std::string GeneratorRuby::ConvertDefault(const StructField& field)
{
    if (field.value)
        return ConvertConstant(*field.type, *field.value, field.optional);

    if (field.array)
        return "Array.new(" + std::to_string(field.N) + ", " + ConvertDefault(*field.type, field.optional) + ")";
    else if (field.vector || field.list || field.set || field.map || field.hash)
        return ConvertTypeName(field) + ".new";

    return ConvertDefault(*field.type, field.optional);
}

std::string GeneratorRuby::ConvertValueToJson(const std::string& type, const std::string& value, bool optional)
{
    if ((type == "char") || (type == "wchar"))
        return "(" + value + ".nil? ? nil : " + value + ".ord)";
    else if (type == "bytes")
        return "(" + value + ".nil? ? nil : Base64.encode64(" + value + ").chomp!)";
    else if (type == "decimal")
        return "(" + value + ".nil? ? nil : " + value + ".to_s('F'))";
    else if (type == "timestamp")
        return "(" + value + ".nil? ? nil : (" + value + ".to_i * 1000000000 + " + value + ".nsec))";
    else if (IsRubyType(type))
        return "(" + value + ".nil? ? nil : " + value + ")";
    else
        return "(" + value + ".nil? ? nil : " + value + ".__to_json_map__)";
}

std::string GeneratorRuby::ConvertValueFromJson(const std::string& type, const std::string& value, bool optional)
{
    if (type == "char")
        return "(" + value + ".nil? ? nil : " + value + ".chr)";
    if (type == "wchar")
        return "(" + value + ".nil? ? nil : " + value + ".chr(Encoding::UTF_8))";
    else if (type == "bytes")
        return "(" + value + ".nil? ? nil : Base64.decode64(" + value + "))";
    else if (type == "decimal")
        return "(" + value + ".nil? ? nil : BigDecimal(" + value + "))";
    else if (type == "timestamp")
        return "(" + value + ".nil? ? nil : Time.at(" + value + " / 1000000000, (" + value + " % 1000000000) / 1000.0).utc)";
    else if (type == "uuid")
        return "(" + value + ".nil? ? nil : UUIDTools::UUID.parse(" + value + "))";
    else if (IsRubyType(type))
        return "(" + value + ".nil? ? nil : " + value + ")";
    else
        return "(" + value + ".nil? ? nil : " + ConvertTitle(type) + ".__from_json_map__(" + value + "))";
}

std::string GeneratorRuby::ConvertKeyFromJson(const std::string& type, const std::string& value, bool optional)
{
    if (type == "byte")
        return value + ".to_i";
    else if (type == "bytes")
        return "Base64.decode64(" + value + ")";
    if ((type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64"))
        return value + ".to_i";
    if ((type == "float") || (type == "double"))
        return value + ".to_f";
    else if (type == "decimal")
        return "BigDecimal(" + value + ")";
    else if (type == "timestamp")
        return "Time.at(" + value + ".to_i / 1000000000, (" + value + ".to_i % 1000000000) / 1000.0).utc";
    else if (type == "uuid")
        return "UUIDTools::UUID.parse(" + value + ")";
    else
        return value;
}

void GeneratorRuby::WriteOutputStreamType(const std::string& type, const std::string& name)
{
    if (type == "bool")
        WriteLineIndent("result << (" + name + " ? 'true' : 'false')");
    else if (type == "bytes")
        WriteLineIndent("result << 'bytes[' << " + name + ".length.to_s << ']'");
    else if ((type == "char") || (type == "wchar"))
        WriteLineIndent("result << \"'\" << " + name + " << \"'\"");
    else if (type == "decimal")
        WriteLineIndent("result << " + name + ".to_s('F')");
    else if ((type == "string") || (type == "uuid"))
        WriteLineIndent("result << '\"' << " + name + ".to_s << '\"'");
    else if (type == "timestamp")
        WriteLineIndent("result << (" + name + ".to_i * 1000000000 + " + name + ".nsec).to_s");
    else
        WriteLineIndent("result << " + name + ".to_s");
}

void GeneratorRuby::WriteOutputStreamValue(const std::string& type, const std::string& name, bool separate)
{
    WriteLineIndent("if !" + name + ".nil?");
    Indent(1);
    if (separate)
        WriteLineIndent("result << (first ? '' : ',')");
    WriteOutputStreamType(type, name);
    Indent(-1);
    WriteLineIndent("else");
    Indent(1);
    if (separate)
        WriteLineIndent("result << (first ? '' : ',')");
    WriteLineIndent("result << 'null'");
    Indent(-1);
    WriteLineIndent("end");
}

} // namespace FBE
